This commit is contained in:
2026-08-30 17:02:19 +08:00
parent 0aa77327d5
commit ca9e7b60e8
40 changed files with 1471 additions and 6 deletions
@@ -0,0 +1,34 @@
package com.baiyue.firstleg.controller;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.dto.LoadingAssociationDtos.Summary;
import com.baiyue.firstleg.service.FirstlegScopeGuard;
import com.baiyue.firstleg.service.LoadingAssociationQueryService;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Validated
@RestController
@RequestMapping("/api/platform/v1/first-leg/loading-associations")
public class PlatformLoadingAssociationController {
@Resource
private LoadingAssociationQueryService service;
@GetMapping("/summary")
@PreAuthorize("hasAuthority('platform:firstleg:smart-loading:list')")
public ApiResponse<Summary> summary(@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) @Positive Long inboundId,
@RequestParam(required = false) @Size(max = 40) String inboundCode) {
FirstlegScopeGuard.platform(user);
return ApiResponse.success(service.summary(inboundId, inboundCode));
}
}
@@ -0,0 +1,33 @@
package com.baiyue.firstleg.dto;
import java.util.List;
/** Admin 统一关联抽屉使用的装柜/PDA只读业务摘要。 */
public final class LoadingAssociationDtos {
private LoadingAssociationDtos() {
}
public record ItemReference(String itemId) {
}
public record Summary(
String inboundId,
String inboundCode,
String orderNo,
String loadingPlanId,
String loadingPlanNo,
String containerBookingId,
String cabinetNo,
String containerNo,
String loadingStatus,
String taskId,
String pdaTaskStatus,
String coLoadGroupId,
String coLoadGroupNo,
List<ItemReference> items
) {
public Summary {
items = items == null ? List.of() : List.copyOf(items);
}
}
}
@@ -0,0 +1,72 @@
package com.baiyue.firstleg.mapper;
import lombok.Getter;
import lombok.Setter;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/** Firstleg 单库内按入仓片段读取装柜、PDA与同柜组稳定关联。 */
@Mapper
public interface LoadingAssociationMapper {
String SELECT_ASSOCIATIONS = """
SELECT inbound.id inboundId,inbound.inbound_code inboundCode,inbound.order_no orderNo,
item.id itemId,plan.id loadingPlanId,plan.plan_no loadingPlanNo,
booking.id containerBookingId,booking.cabinet_no cabinetNo,
booking.container_no containerNo,plan.plan_status loadingStatus,
task.id taskId,
CASE WHEN task.id IS NULL THEN NULL
WHEN task.task_status='COMPLETED' THEN 'COMPLETED'
WHEN task.task_status='COMPLETING' THEN 'PROCESSING'
WHEN task.task_status='COMPLETION_FAILED' THEN 'COMPLETION_FAILED'
WHEN assignment.pick_task_id IS NULL THEN 'UNASSIGNED'
WHEN NOT EXISTS (SELECT 1 FROM fl_loading_pick_item unresolved
WHERE unresolved.pick_task_id=task.id
AND unresolved.item_status IN ('PENDING','PICKING'))
THEN 'AWAITING_REVIEW'
ELSE 'IN_PROGRESS' END pdaTaskStatus,
group_row.id coLoadGroupId,group_row.group_no coLoadGroupNo
FROM fl_inbound_order inbound
LEFT JOIN fl_loading_plan_item item
ON item.account_id=inbound.tenant_id AND item.inbound_id=inbound.id AND item.deleted=0
LEFT JOIN fl_loading_plan plan ON plan.id=item.loading_plan_id
LEFT JOIN fl_container_booking booking ON booking.id=plan.container_booking_id
LEFT JOIN fl_loading_pick_task task ON task.loading_plan_id=plan.id
LEFT JOIN fl_loading_pick_assignment assignment ON assignment.pick_task_id=task.id
LEFT JOIN fl_coload_group_member group_member ON group_member.id=(
SELECT MAX(member_latest.id) FROM fl_coload_group_member member_latest
JOIN fl_coload_group group_latest ON group_latest.id=member_latest.group_id
AND group_latest.group_status<>'CANCELLED'
WHERE member_latest.tenant_id=inbound.tenant_id
AND member_latest.inbound_id=inbound.id)
LEFT JOIN fl_coload_group group_row ON group_row.id=group_member.group_id
WHERE inbound.deleted=0
""";
@Select(SELECT_ASSOCIATIONS + " AND inbound.id=#{inboundId} ORDER BY item.id")
List<Row> byInboundId(@Param("inboundId") long inboundId);
@Select(SELECT_ASSOCIATIONS + " AND inbound.inbound_code=#{inboundCode} ORDER BY inbound.id,item.id")
List<Row> byInboundCode(@Param("inboundCode") String inboundCode);
@Getter
@Setter
class Row {
private Long inboundId;
private String inboundCode;
private String orderNo;
private Long itemId;
private Long loadingPlanId;
private String loadingPlanNo;
private Long containerBookingId;
private String cabinetNo;
private String containerNo;
private String loadingStatus;
private Long taskId;
private String pdaTaskStatus;
private Long coLoadGroupId;
private String coLoadGroupNo;
}
}
@@ -0,0 +1,59 @@
package com.baiyue.firstleg.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.dto.LoadingAssociationDtos.ItemReference;
import com.baiyue.firstleg.dto.LoadingAssociationDtos.Summary;
import com.baiyue.firstleg.mapper.LoadingAssociationMapper;
import com.baiyue.firstleg.mapper.LoadingAssociationMapper.Row;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Objects;
/** 装柜关联只读查询;inboundCode解析和业务摘要编排均不改变现有写接口主键。 */
@Service
public class LoadingAssociationQueryService {
@Resource
private LoadingAssociationMapper mapper;
public Summary summary(Long inboundId, String inboundCode) {
String code = StringUtils.hasText(inboundCode) ? inboundCode.trim() : null;
if ((inboundId == null) == (code == null)) {
throw new BizException(ResultCode.BAD_REQUEST, "inboundId与inboundCode必须且只能提供一个");
}
if (inboundId != null && inboundId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST, "inboundId必须大于0");
}
List<Row> rows = inboundId == null ? mapper.byInboundCode(code) : mapper.byInboundId(inboundId);
if (rows == null || rows.isEmpty()) {
throw new BizException(ResultCode.NOT_FOUND, "入仓片段不存在");
}
Row first = rows.get(0);
requireSingleValue(rows.stream().map(Row::getInboundId).toList(), "inboundCode匹配多个入仓片段");
requireSingleValue(rows.stream().map(Row::getLoadingPlanId).filter(Objects::nonNull).toList(),
"同一入仓片段关联多个有效装柜计划");
requireSingleValue(rows.stream().map(Row::getTaskId).filter(Objects::nonNull).toList(),
"同一入仓片段关联多个PDA任务");
requireSingleValue(rows.stream().map(Row::getCoLoadGroupId).filter(Objects::nonNull).toList(),
"同一入仓片段关联多个有效同柜组");
List<ItemReference> items = rows.stream().map(Row::getItemId).filter(Objects::nonNull).distinct()
.sorted().map(String::valueOf).map(ItemReference::new).toList();
return new Summary(id(first.getInboundId()), first.getInboundCode(), first.getOrderNo(),
id(first.getLoadingPlanId()), first.getLoadingPlanNo(), id(first.getContainerBookingId()),
first.getCabinetNo(), first.getContainerNo(), first.getLoadingStatus(), id(first.getTaskId()),
first.getPdaTaskStatus(), id(first.getCoLoadGroupId()), first.getCoLoadGroupNo(), items);
}
private static void requireSingleValue(List<Long> values, String message) {
if (values.stream().distinct().limit(2).count() > 1) {
throw new BizException(ResultCode.CONFLICT, message);
}
}
private static String id(Long value) {
return value == null ? null : value.toString();
}
}