fix(firstleg): cancel coload groups by inbound code

This commit is contained in:
2026-08-31 14:53:44 +08:00
parent 314ce09b5d
commit ea615b864a
7 changed files with 142 additions and 34 deletions
@@ -6,7 +6,7 @@ import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.BusinessSummary;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CancelRequest;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CancelByInboundCodeRequest;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CreateRequest;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.GroupSnapshot;
import com.baiyue.firstleg.loading.coload.service.ColoadGroupService;
@@ -15,7 +15,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -52,19 +51,19 @@ public class PlatformColoadGroupController {
user.userId(), user.username()));
}
@PostMapping("/{groupId}/cancel")
@PostMapping("/by-inbound-code/{inboundCode}/cancel")
@PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
@BusinessAudit(event = "COLOAD_GROUP_CANCEL", resourceType = "COLOAD_GROUP",
resourceId = "#groupId", description = "整组取消同柜不可拆组")
resourceId = "#inboundCode", description = "按入仓码整组取消同柜不可拆组")
@Operation(summary = "整组取消同柜不可拆组",
description = "仅 ACTIVE 组允许取消;不提供单成员编辑,修改成员必须取消后重建。")
public ApiResponse<GroupSnapshot> cancel(
@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable @Min(1) long groupId,
@PathVariable @Size(min = 1, max = 40) String inboundCode,
@RequestHeader("Idempotency-Key") String idempotencyKey,
@Valid @RequestBody CancelRequest request) {
@Valid @RequestBody CancelByInboundCodeRequest request) {
requirePlatform(user);
return ApiResponse.success(service.cancel(groupId, request.expectedVersion(), request.reason(),
return ApiResponse.success(service.cancelByInboundCode(inboundCode, request.reason(),
idempotencyKey, user.userId(), user.username()));
}
@@ -18,8 +18,7 @@ public final class ColoadGroupModels {
) {
}
public record CancelRequest(
@NotNull @Min(0) Integer expectedVersion,
public record CancelByInboundCodeRequest(
@Size(max = 500) String reason
) {
}
@@ -65,25 +65,30 @@ public class ColoadGroupService implements ColoadGroupAssignmentPort {
}
@Transactional
public GroupSnapshot cancel(long groupId, int expectedVersion, String reason,
String idempotencyKey, long operatorId, String operatorName) {
if (groupId <= 0 || expectedVersion < 0) throw badRequest("组ID或版本不合法");
public GroupSnapshot cancelByInboundCode(String inboundCode, String reason,
String idempotencyKey, long operatorId,
String operatorName) {
String code = normalizedInboundCode(inboundCode);
String key = key(idempotencyKey);
String normalizedReason = trim(reason);
String requestHash = sha256("CANCEL|" + groupId + '|' + expectedVersion + '|'
String requestHash = sha256("CANCEL_BY_INBOUND_CODE|" + code + '|'
+ Objects.toString(normalizedReason, ""));
CommandClaim claim = claim(operatorId, "CANCEL", key, requestHash);
if (claim.replayGroupId() != null) return requiredSnapshot(claim.replayGroupId());
long groupId = requiredCurrentGroupId(code);
GroupRow group = requiredLocked(groupId);
if (!"ACTIVE".equals(group.getGroupStatus())) {
throw conflict("只有未锁定的同柜不可拆组可以整组取消");
}
if (!Objects.equals(group.getVersion(), expectedVersion)
|| mapper.cancel(groupId, expectedVersion, operatorId, trim(operatorName)) != 1) {
GroupSnapshot before = snapshot(group);
int expectedVersion = group.getVersion();
if (mapper.cancel(groupId, expectedVersion, operatorId, trim(operatorName)) != 1) {
throw conflict("组版本已变化,请刷新后重试");
}
mapper.closeMembers(groupId);
if (mapper.closeMembers(groupId) != before.members().size()) {
throw conflict("同柜不可拆组成员状态已变化,请刷新后重试");
}
history(groupId, "CANCEL", "ACTIVE", "CANCELLED", null, key,
normalizedReason, operatorId, operatorName);
complete(claim.commandId(), groupId);
@@ -92,8 +97,7 @@ public class ColoadGroupService implements ColoadGroupAssignmentPort {
@Transactional(readOnly = true)
public BusinessSummary queryByInboundCode(String inboundCode) {
String code = trim(inboundCode);
if (code == null || code.length() > 40) throw badRequest("inboundCode不能为空且不能超过40字符");
String code = normalizedInboundCode(inboundCode);
var groups = mapper.groupsByInboundCode(code).stream()
.filter(row -> row.getGroupId() != null && CURRENT_GROUP_STATUSES.contains(row.getGroupStatus()))
.toList();
@@ -118,6 +122,24 @@ public class ColoadGroupService implements ColoadGroupAssignmentPort {
return new BusinessSummary(status, members.size(), members);
}
private long requiredCurrentGroupId(String inboundCode) {
var groups = mapper.groupsByInboundCode(inboundCode).stream()
.filter(row -> row.getGroupId() != null && CURRENT_GROUP_STATUSES.contains(row.getGroupStatus()))
.toList();
long distinctGroups = groups.stream().map(row -> row.getGroupId()).distinct().limit(2).count();
if (distinctGroups == 0) {
throw new BizException(ResultCode.NOT_FOUND, "入仓码未关联有效同柜不可拆组");
}
if (distinctGroups > 1) throw conflict("入仓码关联多个有效同柜不可拆组");
return groups.get(0).getGroupId();
}
private static String normalizedInboundCode(String inboundCode) {
String code = trim(inboundCode);
if (code == null || code.length() > 40) throw badRequest("inboundCode不能为空且不能超过40字符");
return code;
}
@Transactional(readOnly = true)
public List<InternalOrderGroupInfo> queryInternalByOrderIds(List<Long> requestedOrderIds) {
List<Long> orderIds = normalizedOrderIds(requestedOrderIds, 1, 200);
@@ -331,8 +353,10 @@ public class ColoadGroupService implements ColoadGroupAssignmentPort {
private void history(long groupId, String action, String fromStatus, String toStatus, Long planId,
String operationId, String remark, long operatorId, String operatorName) {
mapper.insertHistory(IdWorker.getId(), groupId, action, fromStatus, toStatus, planId,
operationId, trim(remark), operatorId, trim(operatorName));
if (mapper.insertHistory(IdWorker.getId(), groupId, action, fromStatus, toStatus, planId,
operationId, trim(remark), operatorId, trim(operatorName)) != 1) {
throw conflict("同柜不可拆组审计历史写入冲突");
}
}
private static List<Long> normalizedOrderIds(List<Long> source, int minimum, int maximum) {