feat(firstleg): converge phase3 loading models and pda
This commit is contained in:
+44
-4
@@ -5,6 +5,11 @@ import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PartialPickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.SkipRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.ClaimRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.AssignRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.ReviewRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Operation;
|
||||
import java.util.List;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Task;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
import com.baiyue.firstleg.service.LoadingPickService;
|
||||
@@ -31,7 +36,7 @@ public class PlatformLoadingPdaController {
|
||||
private LoadingPickService service;
|
||||
|
||||
@GetMapping("/tasks")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:smart-loading:list')")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
||||
public ApiResponse<Task> task(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam String containerNo,
|
||||
@RequestParam @Positive long warehouseId) {
|
||||
@@ -39,8 +44,26 @@ public class PlatformLoadingPdaController {
|
||||
return ApiResponse.success(service.task(containerNo, warehouseId));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/claim")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> claim(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@Valid @RequestBody ClaimRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.claim(taskId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/assign")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:assign')")
|
||||
public ApiResponse<Task> assign(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@Valid @RequestBody AssignRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.assign(taskId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/items/{itemId}/pick")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> pick(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@PathVariable @Positive long itemId,
|
||||
@@ -50,7 +73,7 @@ public class PlatformLoadingPdaController {
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/items/{itemId}/partial")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> partial(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@PathVariable @Positive long itemId,
|
||||
@@ -60,7 +83,7 @@ public class PlatformLoadingPdaController {
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/items/{itemId}/skip")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> skip(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@PathVariable @Positive long itemId,
|
||||
@@ -68,4 +91,21 @@ public class PlatformLoadingPdaController {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.skip(taskId, itemId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/review")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:review')")
|
||||
public ApiResponse<Task> review(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@Valid @RequestBody ReviewRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.review(taskId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@GetMapping("/tasks/{taskId}/operations")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
||||
public ApiResponse<List<Operation>> operations(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.operations(taskId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.util.List;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** PDA 装柜拣货接口模型。 */
|
||||
public final class LoadingPickDtos {
|
||||
@@ -41,12 +42,46 @@ public final class LoadingPickDtos {
|
||||
) {
|
||||
}
|
||||
|
||||
public record ClaimRequest(
|
||||
@NotNull @Min(0) Integer expectedAssignmentVersion,
|
||||
@NotBlank @Size(max = 128) String operationId
|
||||
) {
|
||||
}
|
||||
|
||||
public record AssignRequest(
|
||||
@NotNull @Min(1) Long assigneeId,
|
||||
@NotBlank @Size(max = 64) String assigneeName,
|
||||
@NotNull @Min(0) Integer expectedAssignmentVersion,
|
||||
@NotBlank @Size(max = 128) String operationId
|
||||
) {
|
||||
}
|
||||
|
||||
public record ReviewRequest(
|
||||
@NotNull @Min(0) Integer expectedTaskVersion,
|
||||
@NotBlank @Size(max = 128) String operationId,
|
||||
@Size(max = 500) String remark
|
||||
) {
|
||||
}
|
||||
|
||||
public record PickItem(Long id, Long loadingPlanItemId, Long inboundId, Long inboundDetailId,
|
||||
Integer plannedPackageCount, Integer pickedPackageCount,
|
||||
String itemStatus, Integer version) {
|
||||
}
|
||||
|
||||
public record Assignment(Long assigneeId, String assigneeName, Long assignedBy,
|
||||
String assignedByName, String assignmentType, Integer version) {
|
||||
}
|
||||
|
||||
public record Review(String status, Long reviewedBy, String reviewedByName,
|
||||
String remark, Integer version) {
|
||||
}
|
||||
|
||||
public record Operation(Long id, Long pickTaskId, String operationId, String actionType,
|
||||
Long operatorId, String operatorName, LocalDateTime createdAt) {
|
||||
}
|
||||
|
||||
public record Task(Long id, Long loadingPlanId, String containerNo, String taskStatus,
|
||||
Integer version, List<PickItem> items) {
|
||||
String workflowStatus, Integer version, Assignment assignment,
|
||||
Review review, List<PickItem> items) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_loading_pick_assignment")
|
||||
public class LoadingPickAssignmentEntity {
|
||||
@TableId private Long pickTaskId;
|
||||
private Long assigneeId;
|
||||
private String assigneeName;
|
||||
private Long assignedBy;
|
||||
private String assignedByName;
|
||||
private String assignmentType;
|
||||
private Integer version;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_loading_pick_operation")
|
||||
public class LoadingPickOperationEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private Long pickTaskId;
|
||||
private String operationId;
|
||||
private String requestHash;
|
||||
private String actionType;
|
||||
private Long operatorId;
|
||||
private String operatorName;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_loading_pick_review")
|
||||
public class LoadingPickReviewEntity {
|
||||
@TableId private Long pickTaskId;
|
||||
private String reviewStatus;
|
||||
private Long reviewedBy;
|
||||
private String reviewedByName;
|
||||
private String remark;
|
||||
private Integer version;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.baiyue.firstleg.loading.bridge.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.BindRequest;
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
|
||||
import com.baiyue.firstleg.loading.bridge.service.LoadingModelBridgeService;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
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.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Validated
|
||||
@RestController
|
||||
public class PlatformLoadingModelBridgeController {
|
||||
@Resource private LoadingModelBridgeService service;
|
||||
|
||||
@PostMapping("/api/platform/v1/inbounds/{inboundId}/preload/loading-plans/{loadingPlanId}")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
public ApiResponse<Link> bind(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam @Positive long tenantId,
|
||||
@PathVariable @Positive long inboundId, @PathVariable @Positive long loadingPlanId,
|
||||
@RequestHeader("Idempotency-Key") String idempotencyKey,
|
||||
@Valid @RequestBody BindRequest request) {
|
||||
FirstlegScopeGuard.platform(user, tenantId);
|
||||
return ApiResponse.success(service.bind(tenantId, inboundId, loadingPlanId, user.userId(),
|
||||
user.username(), idempotencyKey, request));
|
||||
}
|
||||
|
||||
@GetMapping("/api/platform/v1/inbounds/{inboundId}/preload/loading-plans")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:preload-view')")
|
||||
public ApiResponse<List<Link>> byInbound(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam @Positive long tenantId,
|
||||
@PathVariable @Positive long inboundId) {
|
||||
FirstlegScopeGuard.platform(user, tenantId);
|
||||
return ApiResponse.success(service.byInbound(tenantId, inboundId));
|
||||
}
|
||||
|
||||
@GetMapping("/api/platform/v1/first-leg/smart-loading/plans/{loadingPlanId}/preloads")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:smart-loading:list')")
|
||||
public ApiResponse<List<Link>> byLoadingPlan(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long loadingPlanId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.byLoadingPlan(loadingPlanId));
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.baiyue.firstleg.loading.bridge.dto;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public final class LoadingModelBridgeDtos {
|
||||
private LoadingModelBridgeDtos() {}
|
||||
|
||||
public record BindRequest(
|
||||
@NotNull @Min(0) Integer expectedPreloadVersion,
|
||||
@NotNull @Min(0) Integer expectedLoadingPlanVersion) {}
|
||||
|
||||
public record Link(
|
||||
Long id,
|
||||
Long tenantId,
|
||||
Long inboundId,
|
||||
Long preloadPlanId,
|
||||
String preloadStatus,
|
||||
Integer preloadVersion,
|
||||
Long loadingPlanId,
|
||||
Long containerBookingId,
|
||||
String containerNo,
|
||||
String loadingPlanStatus,
|
||||
Integer loadingPlanVersion,
|
||||
Integer allocatedPackageCount,
|
||||
LocalDateTime linkedAt) {}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.baiyue.firstleg.loading.bridge.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_preload_loading_plan_link")
|
||||
public class PreloadLoadingPlanLinkEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long tenantId;
|
||||
private Long inboundId;
|
||||
private Long preloadPlanId;
|
||||
private Long loadingPlanId;
|
||||
private String idempotencyKey;
|
||||
private String requestHash;
|
||||
private Long createdBy;
|
||||
private String createdByName;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.baiyue.firstleg.loading.bridge.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
|
||||
import com.baiyue.firstleg.loading.bridge.entity.PreloadLoadingPlanLinkEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface LoadingModelBridgeMapper extends BaseMapper<PreloadLoadingPlanLinkEntity> {
|
||||
@Select("SELECT id,tenant_id tenantId,inbound_id inboundId,status,version "
|
||||
+ "FROM fl_preload_plan WHERE tenant_id=#{tenantId} AND inbound_id=#{inboundId}")
|
||||
PreloadSnapshot preload(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
|
||||
@Select("SELECT p.id,p.container_booking_id containerBookingId,b.container_no containerNo,"
|
||||
+ "p.plan_status status,p.version FROM fl_loading_plan p JOIN fl_container_booking b "
|
||||
+ "ON b.id=p.container_booking_id WHERE p.id=#{loadingPlanId}")
|
||||
LoadingPlanSnapshot loadingPlan(@Param("loadingPlanId") long loadingPlanId);
|
||||
|
||||
@Select("SELECT COALESCE(SUM(package_count),0) FROM fl_loading_plan_item "
|
||||
+ "WHERE loading_plan_id=#{loadingPlanId} AND account_id=#{tenantId} "
|
||||
+ "AND inbound_id=#{inboundId} AND deleted=0")
|
||||
int allocatedPackages(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
|
||||
@Param("loadingPlanId") long loadingPlanId);
|
||||
|
||||
@Update("UPDATE fl_preload_plan SET updated_by=#{operatorId},updated_by_name=#{operatorName},"
|
||||
+ "version=version+1 WHERE id=#{preloadPlanId} AND tenant_id=#{tenantId} "
|
||||
+ "AND inbound_id=#{inboundId} AND status='PENDING' AND version=#{expectedVersion}")
|
||||
int touchPreload(@Param("preloadPlanId") long preloadPlanId, @Param("tenantId") long tenantId,
|
||||
@Param("inboundId") long inboundId, @Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
|
||||
@Update("UPDATE fl_loading_plan SET updated_by=#{operatorId},updated_by_name=#{operatorName},"
|
||||
+ "version=version+1 WHERE id=#{loadingPlanId} AND plan_status='EDITING' "
|
||||
+ "AND version=#{expectedVersion}")
|
||||
int touchLoadingPlan(@Param("loadingPlanId") long loadingPlanId,
|
||||
@Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Select(LINK_SELECT + " WHERE link.tenant_id=#{tenantId} AND link.inbound_id=#{inboundId}"
|
||||
+ " ORDER BY link.created_at,link.id")
|
||||
List<Link> byInbound(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
|
||||
@Select(LINK_SELECT + " WHERE link.loading_plan_id=#{loadingPlanId}"
|
||||
+ " ORDER BY link.tenant_id,link.inbound_id,link.id")
|
||||
List<Link> byLoadingPlan(@Param("loadingPlanId") long loadingPlanId);
|
||||
|
||||
String LINK_SELECT = """
|
||||
SELECT link.id,link.tenant_id tenantId,link.inbound_id inboundId,
|
||||
link.preload_plan_id preloadPlanId,preload.status preloadStatus,
|
||||
preload.version preloadVersion,link.loading_plan_id loadingPlanId,
|
||||
plan.container_booking_id containerBookingId,booking.container_no containerNo,
|
||||
plan.plan_status loadingPlanStatus,plan.version loadingPlanVersion,
|
||||
COALESCE((SELECT SUM(item.package_count) FROM fl_loading_plan_item item
|
||||
WHERE item.loading_plan_id=link.loading_plan_id AND item.account_id=link.tenant_id
|
||||
AND item.inbound_id=link.inbound_id AND item.deleted=0),0) allocatedPackageCount,
|
||||
link.created_at linkedAt
|
||||
FROM fl_preload_loading_plan_link link
|
||||
JOIN fl_preload_plan preload ON preload.id=link.preload_plan_id
|
||||
AND preload.tenant_id=link.tenant_id AND preload.inbound_id=link.inbound_id
|
||||
JOIN fl_loading_plan plan ON plan.id=link.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id=plan.container_booking_id
|
||||
""";
|
||||
|
||||
record PreloadSnapshot(Long id, Long tenantId, Long inboundId, String status, Integer version) {}
|
||||
record LoadingPlanSnapshot(Long id, Long containerBookingId, String containerNo,
|
||||
String status, Integer version) {}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.baiyue.firstleg.loading.bridge.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
|
||||
import com.baiyue.firstleg.loading.bridge.entity.PreloadLoadingPlanLinkEntity;
|
||||
import com.baiyue.firstleg.loading.bridge.mapper.LoadingModelBridgeMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public class LoadingModelBridgeRepository {
|
||||
@Resource private LoadingModelBridgeMapper mapper;
|
||||
|
||||
public LoadingModelBridgeMapper.PreloadSnapshot preload(long tenantId, long inboundId) {
|
||||
return mapper.preload(tenantId, inboundId);
|
||||
}
|
||||
|
||||
public LoadingModelBridgeMapper.LoadingPlanSnapshot loadingPlan(long loadingPlanId) {
|
||||
return mapper.loadingPlan(loadingPlanId);
|
||||
}
|
||||
|
||||
public int allocatedPackages(long tenantId, long inboundId, long loadingPlanId) {
|
||||
return mapper.allocatedPackages(tenantId, inboundId, loadingPlanId);
|
||||
}
|
||||
|
||||
public PreloadLoadingPlanLinkEntity byPair(long tenantId, long inboundId, long loadingPlanId) {
|
||||
return mapper.selectOne(new LambdaQueryWrapper<PreloadLoadingPlanLinkEntity>()
|
||||
.eq(PreloadLoadingPlanLinkEntity::getTenantId, tenantId)
|
||||
.eq(PreloadLoadingPlanLinkEntity::getInboundId, inboundId)
|
||||
.eq(PreloadLoadingPlanLinkEntity::getLoadingPlanId, loadingPlanId));
|
||||
}
|
||||
|
||||
public PreloadLoadingPlanLinkEntity byIdempotency(long operatorId, String key) {
|
||||
return mapper.selectOne(new LambdaQueryWrapper<PreloadLoadingPlanLinkEntity>()
|
||||
.eq(PreloadLoadingPlanLinkEntity::getCreatedBy, operatorId)
|
||||
.eq(PreloadLoadingPlanLinkEntity::getIdempotencyKey, key));
|
||||
}
|
||||
|
||||
public void insert(PreloadLoadingPlanLinkEntity value) { mapper.insert(value); }
|
||||
|
||||
public int touchPreload(long preloadPlanId, long tenantId, long inboundId, int version,
|
||||
long operatorId, String operatorName) {
|
||||
return mapper.touchPreload(preloadPlanId, tenantId, inboundId, version, operatorId, operatorName);
|
||||
}
|
||||
|
||||
public int touchLoadingPlan(long loadingPlanId, int version, long operatorId, String operatorName) {
|
||||
return mapper.touchLoadingPlan(loadingPlanId, version, operatorId, operatorName);
|
||||
}
|
||||
|
||||
public List<Link> byInbound(long tenantId, long inboundId) { return mapper.byInbound(tenantId, inboundId); }
|
||||
public List<Link> byLoadingPlan(long loadingPlanId) { return mapper.byLoadingPlan(loadingPlanId); }
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package com.baiyue.firstleg.loading.bridge.service;
|
||||
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.BindRequest;
|
||||
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
|
||||
import com.baiyue.firstleg.loading.bridge.entity.PreloadLoadingPlanLinkEntity;
|
||||
import com.baiyue.firstleg.loading.bridge.repository.LoadingModelBridgeRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class LoadingModelBridgeService {
|
||||
@Resource private LoadingModelBridgeRepository repository;
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "PRELOAD_LOADING_PLAN_BIND", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#loadingPlanId", description = "关联入库预装与柜次装柜计划")
|
||||
public Link bind(long tenantId, long inboundId, long loadingPlanId, long operatorId,
|
||||
String operatorName, String idempotencyKey, BindRequest request) {
|
||||
String key = validateKey(idempotencyKey);
|
||||
String requestHash = hash(tenantId + "|" + inboundId + "|" + loadingPlanId + "|"
|
||||
+ request.expectedPreloadVersion() + "|" + request.expectedLoadingPlanVersion());
|
||||
PreloadLoadingPlanLinkEntity replay = repository.byIdempotency(operatorId, key);
|
||||
if (replay != null) {
|
||||
if (!requestHash.equals(replay.getRequestHash())) throw conflict("Idempotency-Key已用于不同关联请求");
|
||||
return requireLink(tenantId, inboundId, loadingPlanId);
|
||||
}
|
||||
if (repository.byPair(tenantId, inboundId, loadingPlanId) != null) {
|
||||
return requireLink(tenantId, inboundId, loadingPlanId);
|
||||
}
|
||||
|
||||
var preload = repository.preload(tenantId, inboundId);
|
||||
if (preload == null) throw new BizException(ResultCode.NOT_FOUND, "入库单预装计划不存在");
|
||||
var loadingPlan = repository.loadingPlan(loadingPlanId);
|
||||
if (loadingPlan == null) throw new BizException(ResultCode.NOT_FOUND, "柜次装柜计划不存在");
|
||||
if (!"PENDING".equals(preload.status()) || !"EDITING".equals(loadingPlan.status())) {
|
||||
throw conflict("仅待确认预装和编辑中柜次计划可以建立关联");
|
||||
}
|
||||
if (repository.allocatedPackages(tenantId, inboundId, loadingPlanId) <= 0) {
|
||||
throw conflict("柜次计划尚未分配当前入库单货物");
|
||||
}
|
||||
|
||||
PreloadLoadingPlanLinkEntity link = new PreloadLoadingPlanLinkEntity();
|
||||
link.setTenantId(tenantId); link.setInboundId(inboundId); link.setPreloadPlanId(preload.id());
|
||||
link.setLoadingPlanId(loadingPlanId); link.setIdempotencyKey(key); link.setRequestHash(requestHash);
|
||||
link.setCreatedBy(operatorId); link.setCreatedByName(operatorName);
|
||||
try {
|
||||
repository.insert(link);
|
||||
} catch (DuplicateKeyException exception) {
|
||||
throw conflict("相同预装与柜次关联正在处理中,请刷新后重试");
|
||||
}
|
||||
if (repository.touchPreload(preload.id(), tenantId, inboundId, request.expectedPreloadVersion(),
|
||||
operatorId, operatorName) != 1) throw concurrent();
|
||||
if (repository.touchLoadingPlan(loadingPlanId, request.expectedLoadingPlanVersion(),
|
||||
operatorId, operatorName) != 1) throw concurrent();
|
||||
return requireLink(tenantId, inboundId, loadingPlanId);
|
||||
}
|
||||
|
||||
public List<Link> byInbound(long tenantId, long inboundId) { return repository.byInbound(tenantId, inboundId); }
|
||||
public List<Link> byLoadingPlan(long loadingPlanId) { return repository.byLoadingPlan(loadingPlanId); }
|
||||
|
||||
public void requireStarted(long tenantId, long inboundId) {
|
||||
List<Link> links = requireLinks(tenantId, inboundId);
|
||||
if (links.stream().anyMatch(link -> link.allocatedPackageCount() == null || link.allocatedPackageCount() <= 0
|
||||
|| !Set.of("LOADING", "COMPLETED").contains(link.loadingPlanStatus()))) {
|
||||
throw conflict("关联柜次尚未全部开始装柜");
|
||||
}
|
||||
}
|
||||
|
||||
public void requireCompleted(long tenantId, long inboundId) {
|
||||
List<Link> links = requireLinks(tenantId, inboundId);
|
||||
if (links.stream().anyMatch(link -> link.allocatedPackageCount() == null || link.allocatedPackageCount() <= 0
|
||||
|| !"COMPLETED".equals(link.loadingPlanStatus()))) {
|
||||
throw conflict("关联柜次尚未全部完成装柜");
|
||||
}
|
||||
}
|
||||
|
||||
private List<Link> requireLinks(long tenantId, long inboundId) {
|
||||
List<Link> links = repository.byInbound(tenantId, inboundId);
|
||||
if (links.isEmpty()) throw conflict("入库单尚未关联柜次装柜计划");
|
||||
return links;
|
||||
}
|
||||
|
||||
private Link requireLink(long tenantId, long inboundId, long loadingPlanId) {
|
||||
return repository.byInbound(tenantId, inboundId).stream()
|
||||
.filter(link -> Objects.equals(link.loadingPlanId(), loadingPlanId)).findFirst()
|
||||
.orElseThrow(() -> new BizException(ResultCode.NOT_FOUND, "预装与柜次关联不存在"));
|
||||
}
|
||||
|
||||
private static String validateKey(String key) {
|
||||
String value = key == null ? null : key.trim();
|
||||
if (!StringUtils.hasText(value) || value.length() > 128) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "Idempotency-Key不能为空且长度不能超过128");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String hash(String value) {
|
||||
try {
|
||||
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
|
||||
.digest(value.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception impossible) { throw new IllegalStateException(impossible); }
|
||||
}
|
||||
|
||||
private static BizException conflict(String message) { return new BizException(ResultCode.CONFLICT, message); }
|
||||
private static BizException concurrent() { return conflict("数据已被其他人修改,请刷新后重试"); }
|
||||
}
|
||||
+12
@@ -5,9 +5,21 @@ import com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
|
||||
@Mapper
|
||||
public interface InboundLoadingPickTaskMapper extends BaseMapper<InboundLoadingPickTaskEntity> {
|
||||
@Insert("INSERT INTO fl_inbound_loading_pick_task "
|
||||
+ "(id,tenant_id,inbound_id,status,version,expected_count,picked_count,remark,updated_by,updated_by_name) "
|
||||
+ "VALUES (#{id},#{tenantId},#{inboundId},#{status},0,#{expectedCount},#{pickedCount},'柜次PDA自动汇总',#{operatorId},#{operatorName}) "
|
||||
+ "ON DUPLICATE KEY UPDATE status=VALUES(status),expected_count=VALUES(expected_count),"
|
||||
+ "picked_count=VALUES(picked_count),remark='柜次PDA自动汇总',updated_by=VALUES(updated_by),"
|
||||
+ "updated_by_name=VALUES(updated_by_name),version=version+1")
|
||||
int upsertProjection(@Param("id") long id, @Param("tenantId") long tenantId,
|
||||
@Param("inboundId") long inboundId, @Param("status") String status,
|
||||
@Param("expectedCount") int expectedCount, @Param("pickedCount") int pickedCount,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
|
||||
@Update("UPDATE fl_inbound_loading_pick_task SET status=#{toStatus}, updated_by=#{operatorId}, updated_by_name=#{operatorName}, "
|
||||
+ "remark=COALESCE(#{remark}, remark), expected_count=COALESCE(#{expectedCount}, expected_count), "
|
||||
+ "picked_count=COALESCE(#{pickedCount}, picked_count), version=version+1 WHERE tenant_id=#{tenantId} AND inbound_id=#{inboundId} "
|
||||
|
||||
+41
-13
@@ -14,7 +14,9 @@ import com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity;
|
||||
import com.baiyue.firstleg.loading.domain.PickingStatus;
|
||||
import com.baiyue.firstleg.loading.dto.LoadingResponses.PickingResponse;
|
||||
import com.baiyue.firstleg.loading.mapper.LoadingInboundMapper.Snapshot;
|
||||
import com.baiyue.firstleg.loading.bridge.service.LoadingModelBridgeService;
|
||||
import com.baiyue.firstleg.loading.repository.LoadingRepository;
|
||||
import com.baiyue.firstleg.repository.LoadingPickRepository;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import feign.FeignException;
|
||||
@@ -41,6 +43,8 @@ public class LoadingDeliveryService {
|
||||
private static final String COMPLETE_DELIVERY = "COMPLETE_DELIVERY";
|
||||
|
||||
@Resource private LoadingRepository repository;
|
||||
@Resource private LoadingPickRepository loadingPicks;
|
||||
@Resource private LoadingModelBridgeService loadingModelBridge;
|
||||
@Resource private InboundStatusHistoryMapper histories;
|
||||
@Resource private LoadingEvidenceFileClient files;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@@ -100,10 +104,12 @@ public class LoadingDeliveryService {
|
||||
if (replay != null) return replay;
|
||||
Snapshot inbound = requireInbound(tenantId, inboundId);
|
||||
if ("LOADED".equals(inbound.processStatus()) || "DELIVERED".equals(inbound.processStatus())) {
|
||||
loadingModelBridge.requireCompleted(tenantId, inboundId);
|
||||
return complete(tenantId, inboundId, operatorId, COMPLETE_LOADING, key,
|
||||
new LoadingActionResponse(inbound.processStatus(), evidenceResponse(tenantId, inboundId)));
|
||||
}
|
||||
if (!"LOADING".equals(inbound.processStatus())) throw new BizException(ResultCode.CONFLICT, "当前订单不在装柜中");
|
||||
loadingModelBridge.requireCompleted(tenantId, inboundId);
|
||||
requireCompletedPicking(tenantId, inboundId);
|
||||
requireCompletionEvidence(tenantId, inboundId);
|
||||
if (repository.completeLoading(tenantId, inboundId, operatorId, request.expectedVersion()) != 1) throw concurrent();
|
||||
@@ -142,11 +148,8 @@ public class LoadingDeliveryService {
|
||||
LoadingActionResponse replay = claimOrReplay(tenantId, inboundId, operatorId, "START_PICKING", key, hash);
|
||||
if (replay != null) return pickResponse(tenantId, inboundId);
|
||||
requireLoadingStage(tenantId, inboundId);
|
||||
if (request.expectedCount() == null || request.expectedCount() <= 0
|
||||
|| request.pickedCount() == null || request.pickedCount() < 0
|
||||
|| request.pickedCount() > request.expectedCount()) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "拣货计划数量必须大于0且已拣数量不能超过计划数量");
|
||||
}
|
||||
CanonicalPicking canonical = canonicalPicking(tenantId, inboundId);
|
||||
requireMatchingPickingRequest(request, canonical);
|
||||
InboundLoadingPickTaskEntity task = repository.findPickTask(tenantId, inboundId);
|
||||
if (task == null) {
|
||||
if (request.expectedVersion() != 0) throw concurrent();
|
||||
@@ -172,10 +175,9 @@ public class LoadingDeliveryService {
|
||||
LoadingActionResponse replay = claimOrReplay(tenantId, inboundId, operatorId, "COMPLETE_PICKING", key, hash);
|
||||
if (replay != null) return pickResponse(tenantId, inboundId);
|
||||
requireLoadingStage(tenantId, inboundId);
|
||||
if (request.expectedCount() == null || request.expectedCount() <= 0
|
||||
|| request.pickedCount() == null || !request.expectedCount().equals(request.pickedCount())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "拣货复核数量必须与计划数量一致");
|
||||
}
|
||||
CanonicalPicking canonical = canonicalPicking(tenantId, inboundId);
|
||||
requireMatchingPickingRequest(request, canonical);
|
||||
if (!canonical.completed()) throw new BizException(ResultCode.CONFLICT, "柜次PDA任务尚未完成复核");
|
||||
InboundLoadingPickTaskEntity task = repository.findPickTask(tenantId, inboundId);
|
||||
if (task == null || !PickingStatus.IN_PROGRESS.equals(task.getStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, task == null ? "拣货任务尚未开始" : "拣货任务不在作业中");
|
||||
@@ -204,14 +206,40 @@ public class LoadingDeliveryService {
|
||||
}
|
||||
|
||||
private void requireCompletedPicking(long tenantId, long inboundId) {
|
||||
InboundLoadingPickTaskEntity task = repository.findPickTask(tenantId, inboundId);
|
||||
if (task == null || !PickingStatus.COMPLETED.equals(task.getStatus())
|
||||
|| task.getExpectedCount() == null || task.getExpectedCount() <= 0
|
||||
|| !task.getExpectedCount().equals(task.getPickedCount())) {
|
||||
if (!canonicalPicking(tenantId, inboundId).completed()) {
|
||||
throw new BizException(ResultCode.CONFLICT, "PDA拣货复核尚未完成");
|
||||
}
|
||||
}
|
||||
|
||||
private CanonicalPicking canonicalPicking(long tenantId, long inboundId) {
|
||||
var progress = loadingPicks.inboundProgress(tenantId, inboundId);
|
||||
int expected = progress == null || progress.getExpectedCount() == null ? 0 : progress.getExpectedCount();
|
||||
int picked = progress == null || progress.getPickedCount() == null ? 0 : progress.getPickedCount();
|
||||
int tasks = progress == null || progress.getTaskCount() == null ? 0 : progress.getTaskCount();
|
||||
int completed = progress == null || progress.getCompletedTaskCount() == null
|
||||
? 0 : progress.getCompletedTaskCount();
|
||||
int unresolved = progress == null || progress.getUnresolvedCount() == null
|
||||
? 0 : progress.getUnresolvedCount();
|
||||
if (expected <= 0 || tasks <= 0) throw new BizException(ResultCode.CONFLICT, "未找到真实柜次PDA拣货任务");
|
||||
return new CanonicalPicking(expected, picked, tasks, completed, unresolved);
|
||||
}
|
||||
|
||||
private static void requireMatchingPickingRequest(PickingRequest request, CanonicalPicking canonical) {
|
||||
if (request.expectedCount() == null || request.pickedCount() == null
|
||||
|| request.expectedCount() != canonical.expectedCount()
|
||||
|| request.pickedCount() != canonical.pickedCount()) {
|
||||
throw new BizException(ResultCode.CONFLICT, "拣货数量与柜次PDA真实汇总不一致");
|
||||
}
|
||||
}
|
||||
|
||||
private record CanonicalPicking(int expectedCount, int pickedCount, int taskCount,
|
||||
int completedTaskCount, int unresolvedCount) {
|
||||
boolean completed() {
|
||||
return expectedCount > 0 && expectedCount == pickedCount && taskCount > 0
|
||||
&& completedTaskCount == taskCount && unresolvedCount == 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void requireCompletionEvidence(long tenantId, long inboundId) {
|
||||
Map<String, LoadingEvidenceEntity> values = repository.listEvidence(tenantId, inboundId).stream()
|
||||
.collect(Collectors.toMap(LoadingEvidenceEntity::getEvidenceType, Function.identity(), (left, right) -> right));
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface LoadingPickAssignmentMapper extends BaseMapper<LoadingPickAssignmentEntity> {
|
||||
@Insert("INSERT INTO fl_loading_pick_assignment "
|
||||
+ "(pick_task_id,assignee_id,assignee_name,assigned_by,assigned_by_name,assignment_type,version) "
|
||||
+ "VALUES (#{taskId},#{assigneeId},#{assigneeName},#{operatorId},#{operatorName},#{type},0)")
|
||||
int create(@Param("taskId") long taskId, @Param("assigneeId") long assigneeId,
|
||||
@Param("assigneeName") String assigneeName, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName, @Param("type") String type);
|
||||
|
||||
@Update("UPDATE fl_loading_pick_assignment SET assignee_id=#{assigneeId},assignee_name=#{assigneeName},"
|
||||
+ "assigned_by=#{operatorId},assigned_by_name=#{operatorName},assignment_type=#{type},version=version+1 "
|
||||
+ "WHERE pick_task_id=#{taskId} AND version=#{expectedVersion}")
|
||||
int reassign(@Param("taskId") long taskId, @Param("assigneeId") long assigneeId,
|
||||
@Param("assigneeName") String assigneeName, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName, @Param("type") String type,
|
||||
@Param("expectedVersion") int expectedVersion);
|
||||
}
|
||||
@@ -9,6 +9,63 @@ import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface LoadingPickItemMapper extends BaseMapper<LoadingPickItemEntity> {
|
||||
@Update("UPDATE fl_loading_plan_item SET package_count=#{pickedCount},updated_by=#{operatorId},"
|
||||
+ "updated_by_name=#{operatorName},version=version+1 WHERE id=#{planItemId} AND deleted=0 "
|
||||
+ "AND package_count=#{plannedCount}")
|
||||
int settlePartialPlanItem(@Param("planItemId") long planItemId,
|
||||
@Param("pickedCount") int pickedCount,
|
||||
@Param("plannedCount") int plannedCount,
|
||||
@Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("UPDATE fl_loading_pick_item SET planned_package_count=picked_package_count,"
|
||||
+ "item_status='PICKED',version=version+1 WHERE id=#{itemId} AND item_status='PARTIAL' "
|
||||
+ "AND picked_package_count>0 AND picked_package_count<planned_package_count")
|
||||
int settlePartialPickItem(@Param("itemId") long itemId);
|
||||
|
||||
@Update("UPDATE fl_loading_plan_item SET deleted=1,removed_by=#{operatorId},"
|
||||
+ "removed_by_name=#{operatorName},removed_at=CURRENT_TIMESTAMP(3),updated_by=#{operatorId},"
|
||||
+ "updated_by_name=#{operatorName},version=version+1 WHERE id=#{planItemId} AND deleted=0 "
|
||||
+ "AND EXISTS (SELECT 1 FROM fl_loading_pick_item pick_item WHERE pick_item.loading_plan_item_id=#{planItemId} "
|
||||
+ "AND pick_item.item_status='SKIPPED' AND pick_item.picked_package_count=0)")
|
||||
int settleSkippedPlanItem(@Param("planItemId") long planItemId,
|
||||
@Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("""
|
||||
SELECT COALESCE(SUM(plan_item.package_count),0) expectedCount,
|
||||
COALESCE(SUM(pick_item.picked_package_count),0) pickedCount,
|
||||
COUNT(DISTINCT pick_task.id) taskCount,
|
||||
COUNT(DISTINCT CASE WHEN pick_task.task_status='COMPLETED' THEN pick_task.id END) completedTaskCount,
|
||||
SUM(CASE WHEN pick_item.id IS NULL OR pick_item.picked_package_count<>plan_item.package_count THEN 1 ELSE 0 END) unresolvedCount
|
||||
FROM fl_loading_plan_item plan_item
|
||||
JOIN fl_inbound_order inbound_order ON inbound_order.id=plan_item.inbound_id
|
||||
AND inbound_order.tenant_id=#{tenantId} AND inbound_order.deleted=0
|
||||
LEFT JOIN fl_loading_pick_item pick_item ON pick_item.loading_plan_item_id=plan_item.id
|
||||
LEFT JOIN fl_loading_pick_task pick_task ON pick_task.id=pick_item.pick_task_id
|
||||
WHERE plan_item.account_id=#{tenantId} AND plan_item.inbound_id=#{inboundId} AND plan_item.deleted=0
|
||||
""")
|
||||
InboundProgress selectInboundProgress(@Param("tenantId") long tenantId,
|
||||
@Param("inboundId") long inboundId);
|
||||
|
||||
interface InboundProgress {
|
||||
Integer getExpectedCount();
|
||||
Integer getPickedCount();
|
||||
Integer getTaskCount();
|
||||
Integer getCompletedTaskCount();
|
||||
Integer getUnresolvedCount();
|
||||
}
|
||||
|
||||
@Select("""
|
||||
SELECT DISTINCT plan_item.account_id tenantId,plan_item.inbound_id inboundId
|
||||
FROM fl_loading_pick_item pick_item
|
||||
JOIN fl_loading_plan_item plan_item ON plan_item.id=pick_item.loading_plan_item_id
|
||||
WHERE pick_item.pick_task_id=#{taskId}
|
||||
""")
|
||||
java.util.List<InboundOwner> selectInboundOwners(@Param("taskId") long taskId);
|
||||
|
||||
record InboundOwner(Long tenantId, Long inboundId) {}
|
||||
|
||||
@Select("""
|
||||
SELECT pick_item.*
|
||||
FROM fl_loading_pick_item pick_item
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.LoadingPickOperationEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface LoadingPickOperationMapper extends BaseMapper<LoadingPickOperationEntity> {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.LoadingPickReviewEntity;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface LoadingPickReviewMapper extends BaseMapper<LoadingPickReviewEntity> {
|
||||
@Insert("INSERT INTO fl_loading_pick_review "
|
||||
+ "(pick_task_id,review_status,reviewed_by,reviewed_by_name,remark,version) "
|
||||
+ "VALUES (#{taskId},'APPROVED',#{operatorId},#{operatorName},#{remark},0)")
|
||||
int approve(@Param("taskId") long taskId, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName, @Param("remark") String remark);
|
||||
}
|
||||
@@ -5,9 +5,17 @@ import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity> {
|
||||
@Select("SELECT * FROM fl_loading_pick_task WHERE id=#{taskId} FOR UPDATE")
|
||||
LoadingPickTaskEntity selectForUpdate(@Param("taskId") long taskId);
|
||||
|
||||
@Update("UPDATE fl_loading_pick_task SET version=version+1 WHERE id=#{taskId} "
|
||||
+ "AND task_status='LOADING' AND version=#{expectedVersion}")
|
||||
int touch(@Param("taskId") long taskId, @Param("expectedVersion") int expectedVersion);
|
||||
|
||||
@Select("""
|
||||
SELECT DISTINCT task.*
|
||||
FROM fl_loading_pick_task task
|
||||
|
||||
@@ -8,6 +8,14 @@ import com.baiyue.firstleg.mapper.LoadingPickHistoryMapper;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanItemEntity;
|
||||
import com.baiyue.firstleg.mapper.LoadingPickItemMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingPickTaskMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingPickAssignmentMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingPickOperationMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingPickReviewMapper;
|
||||
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickOperationEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickReviewEntity;
|
||||
import com.baiyue.firstleg.loading.mapper.InboundLoadingPickTaskMapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -22,11 +30,27 @@ public class LoadingPickRepository {
|
||||
private LoadingPickItemMapper items;
|
||||
@Resource
|
||||
private LoadingPickHistoryMapper histories;
|
||||
@Resource
|
||||
private LoadingPickAssignmentMapper assignments;
|
||||
@Resource
|
||||
private LoadingPickReviewMapper reviews;
|
||||
@Resource
|
||||
private LoadingPickOperationMapper operations;
|
||||
@Resource
|
||||
private InboundLoadingPickTaskMapper inboundProjections;
|
||||
|
||||
public LoadingPickTaskEntity task(long taskId) {
|
||||
return tasks.selectById(taskId);
|
||||
}
|
||||
|
||||
public LoadingPickTaskEntity taskForUpdate(long taskId) {
|
||||
return tasks.selectForUpdate(taskId);
|
||||
}
|
||||
|
||||
public int touchTask(long taskId, int expectedVersion) {
|
||||
return tasks.touch(taskId, expectedVersion);
|
||||
}
|
||||
|
||||
public LoadingPickTaskEntity taskByPlan(long planId) {
|
||||
return tasks.selectOne(new LambdaQueryWrapper<LoadingPickTaskEntity>()
|
||||
.eq(LoadingPickTaskEntity::getLoadingPlanId, planId));
|
||||
@@ -111,4 +135,78 @@ public class LoadingPickRepository {
|
||||
public void deleteItem(long itemId) {
|
||||
items.deleteById(itemId);
|
||||
}
|
||||
|
||||
public LoadingPickAssignmentEntity assignment(long taskId) {
|
||||
return assignments.selectById(taskId);
|
||||
}
|
||||
|
||||
public int createAssignment(long taskId, long assigneeId, String assigneeName,
|
||||
long operatorId, String operatorName, String type) {
|
||||
return assignments.create(taskId, assigneeId, assigneeName, operatorId, operatorName, type);
|
||||
}
|
||||
|
||||
public int reassign(long taskId, long assigneeId, String assigneeName, long operatorId,
|
||||
String operatorName, String type, int expectedVersion) {
|
||||
return assignments.reassign(taskId, assigneeId, assigneeName, operatorId, operatorName,
|
||||
type, expectedVersion);
|
||||
}
|
||||
|
||||
public LoadingPickReviewEntity review(long taskId) {
|
||||
return reviews.selectById(taskId);
|
||||
}
|
||||
|
||||
public int approve(long taskId, long operatorId, String operatorName, String remark) {
|
||||
return reviews.approve(taskId, operatorId, operatorName, remark);
|
||||
}
|
||||
|
||||
public LoadingPickOperationEntity operation(String operationId) {
|
||||
return operations.selectOne(new LambdaQueryWrapper<LoadingPickOperationEntity>()
|
||||
.eq(LoadingPickOperationEntity::getOperationId, operationId));
|
||||
}
|
||||
|
||||
public void insert(LoadingPickOperationEntity operation) {
|
||||
operations.insert(operation);
|
||||
}
|
||||
|
||||
public List<LoadingPickOperationEntity> operations(long taskId) {
|
||||
return operations.selectList(new LambdaQueryWrapper<LoadingPickOperationEntity>()
|
||||
.eq(LoadingPickOperationEntity::getPickTaskId, taskId)
|
||||
.orderByAsc(LoadingPickOperationEntity::getCreatedAt));
|
||||
}
|
||||
|
||||
public LoadingPickItemMapper.InboundProgress inboundProgress(long tenantId, long inboundId) {
|
||||
return items.selectInboundProgress(tenantId, inboundId);
|
||||
}
|
||||
|
||||
public int settlePartial(long planItemId, long pickItemId, int pickedCount, int plannedCount,
|
||||
long operatorId, String operatorName) {
|
||||
if (items.settlePartialPlanItem(planItemId, pickedCount, plannedCount,
|
||||
operatorId, operatorName) != 1) {
|
||||
return 0;
|
||||
}
|
||||
return items.settlePartialPickItem(pickItemId);
|
||||
}
|
||||
|
||||
public int settleSkipped(long planItemId, long operatorId, String operatorName) {
|
||||
return items.settleSkippedPlanItem(planItemId, operatorId, operatorName);
|
||||
}
|
||||
|
||||
public void syncInboundProjections(long taskId, long operatorId, String operatorName) {
|
||||
for (LoadingPickItemMapper.InboundOwner owner : items.selectInboundOwners(taskId)) {
|
||||
LoadingPickItemMapper.InboundProgress progress = inboundProgress(owner.tenantId(), owner.inboundId());
|
||||
int taskCount = value(progress == null ? null : progress.getTaskCount());
|
||||
int completed = value(progress == null ? null : progress.getCompletedTaskCount());
|
||||
int unresolved = value(progress == null ? null : progress.getUnresolvedCount());
|
||||
int expected = value(progress == null ? null : progress.getExpectedCount());
|
||||
int picked = value(progress == null ? null : progress.getPickedCount());
|
||||
String status = taskCount > 0 && completed == taskCount && unresolved == 0 && expected == picked
|
||||
? "COMPLETED" : picked > 0 ? "IN_PROGRESS" : "PENDING";
|
||||
inboundProjections.upsertProjection(IdWorker.getId(), owner.tenantId(), owner.inboundId(), status,
|
||||
expected, picked, operatorId, operatorName);
|
||||
}
|
||||
}
|
||||
|
||||
private static int value(Integer value) {
|
||||
return value == null ? 0 : value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,17 @@ import com.baiyue.firstleg.dto.LoadingPickDtos.PickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PartialPickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.SkipRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Task;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Assignment;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Review;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.ClaimRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.AssignRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.ReviewRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Operation;
|
||||
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickHistoryEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickItemEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickOperationEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickReviewEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanItemEntity;
|
||||
import com.baiyue.firstleg.repository.LoadingPickRepository;
|
||||
@@ -55,6 +64,7 @@ public class LoadingPickService {
|
||||
for (LoadingPlanItemEntity planItem : planItems) {
|
||||
syncItem(task.getId(), planItem);
|
||||
}
|
||||
repository.syncInboundProjections(task.getId(), operatorId, operatorName);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -79,11 +89,59 @@ public class LoadingPickService {
|
||||
return view(task, warehouseId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Task claim(long taskId, ClaimRequest request, long operatorId, String operatorName) {
|
||||
String operationId = operation(request.operationId());
|
||||
String requestHash = hash("CLAIM|" + taskId + '|' + request.expectedAssignmentVersion());
|
||||
Task replay = replayTaskOperation(taskId, operationId, requestHash, operatorId);
|
||||
if (replay != null) return replay;
|
||||
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
|
||||
LoadingPickAssignmentEntity existing = repository.assignment(taskId);
|
||||
if (existing == null) {
|
||||
if (request.expectedAssignmentVersion() != 0
|
||||
|| repository.createAssignment(taskId, operatorId, operatorName, operatorId,
|
||||
operatorName, "CLAIMED") != 1) {
|
||||
throw conflict("PDA任务已被其他作业员领取");
|
||||
}
|
||||
} else {
|
||||
if (!Long.valueOf(operatorId).equals(existing.getAssigneeId())) {
|
||||
throw conflict("PDA任务已分配给其他作业员");
|
||||
}
|
||||
if (!request.expectedAssignmentVersion().equals(existing.getVersion())) {
|
||||
throw conflict("PDA任务分配版本已变化");
|
||||
}
|
||||
}
|
||||
insertOperation(taskId, operationId, requestHash, "CLAIM", operatorId, operatorName);
|
||||
return view(task, null);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Task assign(long taskId, AssignRequest request, long operatorId, String operatorName) {
|
||||
String operationId = operation(request.operationId());
|
||||
String assigneeName = request.assigneeName().trim();
|
||||
String requestHash = hash("ASSIGN|" + taskId + '|' + request.assigneeId() + '|'
|
||||
+ assigneeName + '|' + request.expectedAssignmentVersion());
|
||||
Task replay = replayTaskOperation(taskId, operationId, requestHash, operatorId);
|
||||
if (replay != null) return replay;
|
||||
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
|
||||
LoadingPickAssignmentEntity existing = repository.assignment(taskId);
|
||||
int changed = existing == null
|
||||
? request.expectedAssignmentVersion() == 0
|
||||
? repository.createAssignment(taskId, request.assigneeId(), assigneeName, operatorId,
|
||||
operatorName, "ASSIGNED") : 0
|
||||
: repository.reassign(taskId, request.assigneeId(), assigneeName, operatorId, operatorName,
|
||||
"ASSIGNED", request.expectedAssignmentVersion());
|
||||
if (changed != 1) throw conflict("PDA任务分配版本已变化");
|
||||
insertOperation(taskId, operationId, requestHash, "ASSIGN", operatorId, operatorName);
|
||||
return view(task, null);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Task pick(long taskId, long itemId, PickRequest request, long operatorId, String operatorName) {
|
||||
String operationId = request.operationId().trim();
|
||||
String requestHash = hash(taskId + "|" + itemId + "|" + request.warehouseId()
|
||||
+ "|" + request.pickedPackageCount());
|
||||
requireNoTaskOperation(operationId);
|
||||
LoadingPickHistoryEntity replay = repository.history(operationId);
|
||||
if (replay != null) {
|
||||
if (!replay.getPickTaskId().equals(taskId) || !replay.getPickItemId().equals(itemId)
|
||||
@@ -92,10 +150,7 @@ public class LoadingPickService {
|
||||
}
|
||||
return view(requiredTask(taskId), request.warehouseId());
|
||||
}
|
||||
LoadingPickTaskEntity task = requiredTask(taskId);
|
||||
if (!"LOADING".equals(task.getTaskStatus())) {
|
||||
throw conflict("装柜任务已完成,不能继续拣货");
|
||||
}
|
||||
LoadingPickTaskEntity task = requiredAssignedLoadingTaskForUpdate(taskId, operatorId);
|
||||
LoadingPickItemEntity item = repository.item(taskId, itemId, request.warehouseId());
|
||||
if (item == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货明细不存在");
|
||||
@@ -113,7 +168,7 @@ public class LoadingPickService {
|
||||
history.setOperatorId(operatorId);
|
||||
history.setOperatorName(operatorName);
|
||||
repository.insert(history);
|
||||
finishIfResolved(task, operatorId, operatorName);
|
||||
touchAndSync(task, operatorId, operatorName);
|
||||
return view(requiredTask(taskId), request.warehouseId());
|
||||
}
|
||||
|
||||
@@ -123,19 +178,20 @@ public class LoadingPickService {
|
||||
String operationId = request.operationId().trim();
|
||||
String requestHash = hash("PARTIAL|" + taskId + "|" + itemId + "|" + request.warehouseId()
|
||||
+ "|" + request.finalPickedPackageCount());
|
||||
requireNoTaskOperation(operationId);
|
||||
LoadingPickHistoryEntity replay = repository.history(operationId);
|
||||
if (replay != null) {
|
||||
assertReplay(replay, taskId, itemId, requestHash);
|
||||
return view(requiredTask(taskId), request.warehouseId());
|
||||
}
|
||||
LoadingPickTaskEntity task = requiredLoadingTask(taskId);
|
||||
LoadingPickTaskEntity task = requiredAssignedLoadingTaskForUpdate(taskId, operatorId);
|
||||
requireWarehouseItem(taskId, itemId, request.warehouseId());
|
||||
if (repository.partial(itemId, request.finalPickedPackageCount(), request.version(), request.warehouseId()) != 1) {
|
||||
throw conflict("部分拣货箱数必须大于已拣箱数且小于计划箱数,或明细版本已变化");
|
||||
}
|
||||
insertHistory(taskId, itemId, operationId, requestHash, "PARTIAL",
|
||||
request.finalPickedPackageCount(), null, operatorId, operatorName);
|
||||
finishIfResolved(task, operatorId, operatorName);
|
||||
touchAndSync(task, operatorId, operatorName);
|
||||
return view(requiredTask(taskId), request.warehouseId());
|
||||
}
|
||||
|
||||
@@ -145,26 +201,68 @@ public class LoadingPickService {
|
||||
String reason = trim(request.reason());
|
||||
String requestHash = hash("SKIP|" + taskId + "|" + itemId + "|" + request.warehouseId()
|
||||
+ "|" + reason);
|
||||
requireNoTaskOperation(operationId);
|
||||
LoadingPickHistoryEntity replay = repository.history(operationId);
|
||||
if (replay != null) {
|
||||
assertReplay(replay, taskId, itemId, requestHash);
|
||||
return view(requiredTask(taskId), request.warehouseId());
|
||||
}
|
||||
LoadingPickTaskEntity task = requiredLoadingTask(taskId);
|
||||
LoadingPickTaskEntity task = requiredAssignedLoadingTaskForUpdate(taskId, operatorId);
|
||||
requireWarehouseItem(taskId, itemId, request.warehouseId());
|
||||
if (repository.skip(itemId, request.version(), request.warehouseId()) != 1) {
|
||||
throw conflict("跳拣仅允许未开始拣货的待拣明细,或明细版本已变化");
|
||||
}
|
||||
insertHistory(taskId, itemId, operationId, requestHash, "SKIP", 0, reason,
|
||||
operatorId, operatorName);
|
||||
finishIfResolved(task, operatorId, operatorName);
|
||||
touchAndSync(task, operatorId, operatorName);
|
||||
return view(requiredTask(taskId), request.warehouseId());
|
||||
}
|
||||
|
||||
private void finishIfResolved(LoadingPickTaskEntity task, long operatorId, String operatorName) {
|
||||
if (repository.allPlanItemsPicked(task.getLoadingPlanId())) {
|
||||
loadingListService.finishFromPda(task.getLoadingPlanId(), task.getId(), operatorId, operatorName);
|
||||
@Transactional
|
||||
public Task review(long taskId, ReviewRequest request, long operatorId, String operatorName) {
|
||||
String operationId = operation(request.operationId());
|
||||
String remark = trim(request.remark());
|
||||
String requestHash = hash("REVIEW|" + taskId + '|' + request.expectedTaskVersion() + '|' + remark);
|
||||
Task replay = replayTaskOperation(taskId, operationId, requestHash, operatorId);
|
||||
if (replay != null) return replay;
|
||||
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
|
||||
if (!request.expectedTaskVersion().equals(task.getVersion())) {
|
||||
throw conflict("PDA任务版本已变化,请刷新后复核");
|
||||
}
|
||||
if (repository.review(taskId) != null) throw conflict("PDA任务已经复核");
|
||||
List<LoadingPickItemEntity> taskItems = repository.items(taskId);
|
||||
PdaPickSettlementPlan.Resolution resolution = PdaPickSettlementPlan.resolve(taskItems);
|
||||
for (List<PdaPickSettlementPlan.SplitLine> lines : resolution.partialByInbound().values()) {
|
||||
for (PdaPickSettlementPlan.SplitLine line : lines) {
|
||||
if (repository.settlePartial(line.loadingPlanItemId(), line.pickItemId(),
|
||||
line.pickedPackageCount(), line.pickedPackageCount() + line.remainingPackageCount(),
|
||||
operatorId, operatorName) != 1) {
|
||||
throw conflict("部分拣货结算版本已变化");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Long planItemId : resolution.skippedPlanItemIds()) {
|
||||
if (repository.settleSkipped(planItemId, operatorId, operatorName) != 1) {
|
||||
throw conflict("跳拣结算版本已变化");
|
||||
}
|
||||
}
|
||||
if (!repository.allPlanItemsPicked(task.getLoadingPlanId())) {
|
||||
throw conflict("柜次仍有未完成PDA拣货的箱数");
|
||||
}
|
||||
if (repository.approve(taskId, operatorId, operatorName, remark) != 1) {
|
||||
throw conflict("PDA任务已经复核");
|
||||
}
|
||||
insertOperation(taskId, operationId, requestHash, "REVIEW", operatorId, operatorName);
|
||||
loadingListService.finishFromPda(task.getLoadingPlanId(), taskId, operatorId, operatorName);
|
||||
repository.syncInboundProjections(taskId, operatorId, operatorName);
|
||||
return view(requiredTask(taskId), null);
|
||||
}
|
||||
|
||||
public List<Operation> operations(long taskId) {
|
||||
requiredTask(taskId);
|
||||
return repository.operations(taskId).stream().map(item -> new Operation(item.getId(), item.getPickTaskId(),
|
||||
item.getOperationId(), item.getActionType(), item.getOperatorId(), item.getOperatorName(),
|
||||
item.getCreatedAt())).toList();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -218,19 +316,89 @@ public class LoadingPickService {
|
||||
return task;
|
||||
}
|
||||
|
||||
private LoadingPickTaskEntity requiredLoadingTaskForUpdate(long taskId) {
|
||||
LoadingPickTaskEntity task = repository.taskForUpdate(taskId);
|
||||
if (task == null) throw new BizException(ResultCode.NOT_FOUND, "PDA拣货任务不存在");
|
||||
if (!"LOADING".equals(task.getTaskStatus())) throw conflict("装柜任务已完成,不能继续拣货");
|
||||
return task;
|
||||
}
|
||||
|
||||
private LoadingPickTaskEntity requiredAssignedLoadingTaskForUpdate(long taskId, long operatorId) {
|
||||
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
|
||||
LoadingPickAssignmentEntity assignment = repository.assignment(taskId);
|
||||
if (assignment == null) throw conflict("请先领取PDA拣货任务");
|
||||
if (!Long.valueOf(operatorId).equals(assignment.getAssigneeId())) {
|
||||
throw conflict("PDA拣货任务已分配给其他作业员");
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private void requireWarehouseItem(long taskId, long itemId, long warehouseId) {
|
||||
if (repository.item(taskId, itemId, warehouseId) == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货明细不存在或不属于当前仓库");
|
||||
}
|
||||
}
|
||||
|
||||
private Task view(LoadingPickTaskEntity task, long warehouseId) {
|
||||
List<PickItem> items = repository.items(task.getId(), warehouseId).stream()
|
||||
private Task view(LoadingPickTaskEntity task, Long warehouseId) {
|
||||
List<LoadingPickItemEntity> source = warehouseId == null
|
||||
? repository.items(task.getId()) : repository.items(task.getId(), warehouseId);
|
||||
List<PickItem> items = source.stream()
|
||||
.map(item -> new PickItem(item.getId(), item.getLoadingPlanItemId(), item.getInboundId(),
|
||||
item.getInboundDetailId(), item.getPlannedPackageCount(), item.getPickedPackageCount(),
|
||||
item.getItemStatus(), item.getVersion())).toList();
|
||||
LoadingPickAssignmentEntity assigned = repository.assignment(task.getId());
|
||||
LoadingPickReviewEntity reviewed = repository.review(task.getId());
|
||||
Assignment assignment = assigned == null ? null : new Assignment(assigned.getAssigneeId(),
|
||||
assigned.getAssigneeName(), assigned.getAssignedBy(), assigned.getAssignedByName(),
|
||||
assigned.getAssignmentType(), assigned.getVersion());
|
||||
Review review = reviewed == null ? null : new Review(reviewed.getReviewStatus(),
|
||||
reviewed.getReviewedBy(), reviewed.getReviewedByName(), reviewed.getRemark(), reviewed.getVersion());
|
||||
String workflowStatus = "COMPLETED".equals(task.getTaskStatus()) ? "COMPLETED"
|
||||
: assigned == null ? "UNASSIGNED"
|
||||
: repository.allTaskItemsResolved(task.getId()) ? "AWAITING_REVIEW" : "IN_PROGRESS";
|
||||
return new Task(task.getId(), task.getLoadingPlanId(), task.getContainerNo(), task.getTaskStatus(),
|
||||
task.getVersion(), items);
|
||||
workflowStatus, task.getVersion(), assignment, review, items);
|
||||
}
|
||||
|
||||
private void touchAndSync(LoadingPickTaskEntity task, long operatorId, String operatorName) {
|
||||
if (repository.touchTask(task.getId(), task.getVersion()) != 1) {
|
||||
throw conflict("PDA任务版本已变化");
|
||||
}
|
||||
repository.syncInboundProjections(task.getId(), operatorId, operatorName);
|
||||
}
|
||||
|
||||
private Task replayTaskOperation(long taskId, String operationId, String requestHash, long operatorId) {
|
||||
if (repository.history(operationId) != null) throw conflict("operationId已用于明细拣货操作");
|
||||
LoadingPickOperationEntity replay = repository.operation(operationId);
|
||||
if (replay == null) return null;
|
||||
if (!Long.valueOf(taskId).equals(replay.getPickTaskId())
|
||||
|| !Long.valueOf(operatorId).equals(replay.getOperatorId())
|
||||
|| !requestHash.equals(replay.getRequestHash())) {
|
||||
throw conflict("operationId已用于不同PDA任务操作");
|
||||
}
|
||||
return view(requiredTask(taskId), null);
|
||||
}
|
||||
|
||||
private void insertOperation(long taskId, String operationId, String requestHash, String actionType,
|
||||
long operatorId, String operatorName) {
|
||||
LoadingPickOperationEntity entity = new LoadingPickOperationEntity();
|
||||
entity.setPickTaskId(taskId); entity.setOperationId(operationId); entity.setRequestHash(requestHash);
|
||||
entity.setActionType(actionType); entity.setOperatorId(operatorId); entity.setOperatorName(operatorName);
|
||||
try {
|
||||
repository.insert(entity);
|
||||
} catch (DuplicateKeyException exception) {
|
||||
throw conflict("operationId已被并发使用");
|
||||
}
|
||||
}
|
||||
|
||||
private void requireNoTaskOperation(String operationId) {
|
||||
if (repository.operation(operationId) != null) throw conflict("operationId已用于PDA任务操作");
|
||||
}
|
||||
|
||||
private static String operation(String value) {
|
||||
String result = trim(value);
|
||||
if (!StringUtils.hasText(result)) throw new BizException(ResultCode.BAD_REQUEST, "operationId不能为空");
|
||||
return result;
|
||||
}
|
||||
|
||||
private void insertHistory(long taskId, long itemId, String operationId, String requestHash,
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.split.service;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
|
||||
import com.baiyue.firstleg.loading.bridge.service.LoadingModelBridgeService;
|
||||
import com.baiyue.firstleg.mapper.InboundStatusHistoryMapper;
|
||||
import com.baiyue.firstleg.split.domain.PreloadStatus;
|
||||
import com.baiyue.firstleg.split.dto.SplitRequests.ConfirmPreloadRequest;
|
||||
@@ -33,6 +34,7 @@ public class SplitPreloadService {
|
||||
private static final String CONFIRM_PRELOAD = "CONFIRM_PRELOAD";
|
||||
|
||||
@Resource private SplitRepository repository;
|
||||
@Resource private LoadingModelBridgeService loadingModelBridge;
|
||||
@Resource private InboundStatusHistoryMapper histories;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
|
||||
@@ -100,6 +102,7 @@ public class SplitPreloadService {
|
||||
if (replay != null) return replay;
|
||||
InboundSnapshot inbound = requireInbound(tenantId, inboundId);
|
||||
if ("LOADING".equals(inbound.processStatus())) {
|
||||
loadingModelBridge.requireStarted(tenantId, inboundId);
|
||||
return complete(tenantId, inboundId, operatorId, CONFIRM_PRELOAD, key,
|
||||
new SplitActionResponse("LOADING", response(repository.findPlan(tenantId, inboundId))));
|
||||
}
|
||||
@@ -108,6 +111,7 @@ public class SplitPreloadService {
|
||||
if (PreloadStatus.CONFIRMED.equals(plan.getStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "预装清单已确认但订单状态尚未推进,请刷新重试");
|
||||
}
|
||||
loadingModelBridge.requireStarted(tenantId, inboundId);
|
||||
if (repository.changePlanStatus(tenantId, inboundId, plan.getStatus(), PreloadStatus.CONFIRMED,
|
||||
operatorId, operatorName, request.expectedVersion()) != 1) {
|
||||
throw concurrent();
|
||||
|
||||
Reference in New Issue
Block a user