货柜pda 添加操作人
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package com.baiyue.firstleg.client;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@FeignClient(name = "iam-service", contextId = "firstlegDomesticPdaOperatorClient",
|
||||
url = "${baiyue.clients.iam.url:}", path = "/internal/iam")
|
||||
public interface DomesticPdaOperatorClient {
|
||||
@GetMapping("/domestic-pda-operators/{authUserId}")
|
||||
ApiResponse<Operator> required(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@PathVariable long authUserId,
|
||||
@RequestParam long warehouseId);
|
||||
|
||||
record Operator(Long userId, String displayName) { }
|
||||
}
|
||||
+2
-2
@@ -92,9 +92,9 @@ public class PlatformLoadingListController {
|
||||
planId, warehouseId, user.userId(), user.username(), idempotencyKey, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "确认整份预装计划", description = "按 loadingPlanId 原子确认全部已分配入库预装票据;要求关联订舱已放舱,真实集装箱号可在PDA确认装柜完成前补录。成功后唯一创建待领取 PDA 任务,claim 后才进入 LOADING。")
|
||||
@Operation(summary = "确认预装并推送指定 PDA 作业员", description = "按 loadingPlanId 原子确认预装、创建任务并分配给启用的国内头程 PDA 作业员;仅被指定人员可在 PDA 查看和领取。")
|
||||
@PostMapping("/loading-plans/{planId}/confirm")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:preload:edit')")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:preload:edit') and hasAuthority('platform:firstleg:pda:assign')")
|
||||
public ApiResponse<PreloadConfirmationResponse> confirmPreload(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long planId,
|
||||
|
||||
+11
-1
@@ -64,7 +64,9 @@ public class PlatformLoadingPdaController {
|
||||
@RequestParam(required = false) Long loadingPlanId,
|
||||
@RequestParam @Positive long warehouseId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.task(cabinetNo, loadingPlanId, warehouseId));
|
||||
Task result = service.task(cabinetNo, loadingPlanId, warehouseId);
|
||||
service.requireVisibleTo(result.id(), user.userId(), canManage(user));
|
||||
return ApiResponse.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/tasks/pending")
|
||||
@@ -90,6 +92,7 @@ public class PlatformLoadingPdaController {
|
||||
@PathVariable @Positive long taskId,
|
||||
@RequestParam @Positive long warehouseId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
service.requireVisibleTo(taskId, user.userId(), canManage(user));
|
||||
return ApiResponse.success(service.task(taskId, warehouseId));
|
||||
}
|
||||
|
||||
@@ -143,6 +146,7 @@ public class PlatformLoadingPdaController {
|
||||
@PathVariable @Positive long taskId,
|
||||
@RequestParam @Positive long warehouseId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
service.requireVisibleTo(taskId, user.userId(), canManage(user));
|
||||
return ApiResponse.success(service.inventorySnapshot(taskId, warehouseId));
|
||||
}
|
||||
|
||||
@@ -238,7 +242,13 @@ public class PlatformLoadingPdaController {
|
||||
public ApiResponse<List<Operation>> operations(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
service.requireVisibleTo(taskId, user.userId(), canManage(user));
|
||||
return ApiResponse.success(service.operations(taskId));
|
||||
}
|
||||
|
||||
private static boolean canManage(AuthenticatedUser user) {
|
||||
return user.permissions().contains("platform:firstleg:pda:assign")
|
||||
|| user.permissions().contains("platform:firstleg:pda:review");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -102,7 +102,8 @@ public final class LoadingListDtos {
|
||||
}
|
||||
}
|
||||
|
||||
public record ConfirmPreloadRequest(@NotNull @Min(0) Integer expectedVersion) {
|
||||
public record ConfirmPreloadRequest(@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotNull @Min(1) Long assigneeId) {
|
||||
}
|
||||
|
||||
public record PreloadConfirmationResponse(
|
||||
|
||||
@@ -108,7 +108,7 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
|
||||
LEFT JOIN fl_loading_pick_assignment assignment ON assignment.pick_task_id = task.id
|
||||
WHERE task.task_status IN ('LOADING','COMPLETING','COMPLETION_FAILED') AND booking.record_status = 'ACTIVE'
|
||||
AND booking.warehouse_id = #{warehouseId}
|
||||
AND (assignment.pick_task_id IS NULL OR assignment.assignee_id = #{operatorId})
|
||||
AND assignment.assignee_id = #{operatorId}
|
||||
<if test="keyword != null and keyword != ''">
|
||||
AND (booking.container_no LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR booking.warehouse_name LIKE CONCAT('%', #{keyword}, '%'))
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.baiyue.firstleg.dto.LoadingListDtos.DocumentAttachRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.DocumentCompleteRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.LifecycleResolution;
|
||||
import com.baiyue.firstleg.client.LoadingDocumentFileClient;
|
||||
import com.baiyue.firstleg.client.DomesticPdaOperatorClient;
|
||||
import com.baiyue.firstleg.entity.ContainerDocumentEntity;
|
||||
import com.baiyue.firstleg.mapper.LoadingListQueryMapper.AggregateRow;
|
||||
import com.baiyue.firstleg.mapper.LoadingListQueryMapper.HistoryRow;
|
||||
@@ -78,6 +79,8 @@ public class LoadingListService {
|
||||
private ObjectMapper objectMapper;
|
||||
@Resource
|
||||
private LoadingDocumentFileClient documentFiles;
|
||||
@Resource
|
||||
private DomesticPdaOperatorClient domesticPdaOperators;
|
||||
@Value("${baiyue.security.internal-service-token:}")
|
||||
private String internalToken;
|
||||
|
||||
@@ -509,12 +512,21 @@ public class LoadingListService {
|
||||
String idempotencyKey,
|
||||
ConfirmPreloadRequest request) {
|
||||
inboundHoldGuard.requirePlanNotHeld(planId);
|
||||
String requestHash = sha256("CONFIRM_PRELOAD|" + planId + '|' + request.expectedVersion());
|
||||
String requestHash = sha256("CONFIRM_PRELOAD|" + planId + '|' + request.expectedVersion()
|
||||
+ '|' + request.assigneeId());
|
||||
String operationId = operationId("CONFIRM_PRELOAD", planId, idempotencyKey);
|
||||
HistoryRow replay = repository.history(operationId);
|
||||
if (replay != null) return confirmationReplay(replay, requestHash);
|
||||
|
||||
AggregateRow before = requirePlan(planId);
|
||||
var operatorResponse = domesticPdaOperators.required(internalToken, request.assigneeId(), before.getWarehouseId());
|
||||
var assignee = operatorResponse == null ? null : operatorResponse.data();
|
||||
if (operatorResponse == null || !operatorResponse.successful() || assignee == null
|
||||
|| !request.assigneeId().equals(assignee.userId())
|
||||
|| !StringUtils.hasText(assignee.displayName())) {
|
||||
throw badRequest("所选国内头程 PDA 作业员无效");
|
||||
}
|
||||
|
||||
List<Link> links = loadingPickService.requirePreloadConfirmationReady(planId);
|
||||
requireContainerFeesReadyForPreloadConfirmation(links, operationId);
|
||||
if (repository.touchForPreloadConfirmation(planId, request.expectedVersion(),
|
||||
@@ -533,6 +545,12 @@ public class LoadingListService {
|
||||
.forEach(containerBookingId -> loadingPickService.beginIfReady(
|
||||
containerBookingId, operatorId, operatorName));
|
||||
|
||||
var pickTask = pickRepository.taskByPlan(planId);
|
||||
if (pickTask == null || pickRepository.createAssignment(pickTask.getId(), assignee.userId(),
|
||||
assignee.displayName().trim(), operatorId, trim(operatorName), "ASSIGNED") != 1) {
|
||||
throw conflict("PDA任务创建或分配失败,请刷新后重试");
|
||||
}
|
||||
|
||||
AggregateRow after = requirePlan(planId);
|
||||
PreloadDetail detail = repository.preloadDetail(after);
|
||||
PreloadConfirmationResponse response = new PreloadConfirmationResponse(planId,
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.baiyue.firstleg.dto.LoadingPickDtos.InventorySnapshot;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.InventoryUnit;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PendingTask;
|
||||
import com.baiyue.firstleg.domain.Iso6346ContainerNumber;
|
||||
import com.baiyue.firstleg.client.DomesticPdaOperatorClient;
|
||||
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickHistoryEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickItemEntity;
|
||||
@@ -54,6 +55,7 @@ import com.baiyue.firstleg.oceanbooking.repository.OceanBookingRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -110,6 +112,10 @@ public class LoadingPickService {
|
||||
private InboundHoldGuard inboundHoldGuard;
|
||||
@Resource
|
||||
private com.baiyue.firstleg.mapper.DropshipPalletizationMapper dropshipPalletizationMapper;
|
||||
@Resource
|
||||
private DomesticPdaOperatorClient domesticPdaOperators;
|
||||
@Value("${baiyue.security.internal-service-token:}")
|
||||
private String internalToken;
|
||||
|
||||
/** Opens the field task after planning and ocean release; the real container number may be filled later. */
|
||||
@Transactional
|
||||
@@ -238,6 +244,15 @@ public class LoadingPickService {
|
||||
return view(requiredTask(taskId), warehouseId);
|
||||
}
|
||||
|
||||
/** PDA reads require the actual assigned operator; backstage dispatch/review retains oversight. */
|
||||
public void requireVisibleTo(long taskId, long operatorId, boolean dispatcherOrReviewer) {
|
||||
if (dispatcherOrReviewer) return;
|
||||
var assignment = repository.assignment(taskId);
|
||||
if (assignment == null || !Long.valueOf(operatorId).equals(assignment.getAssigneeId())) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "PDA任务不存在或未分配给当前作业员");
|
||||
}
|
||||
}
|
||||
|
||||
public List<PendingTask> pendingTasks(long operatorId, long warehouseId, String keyword) {
|
||||
String normalizedKeyword = StringUtils.hasText(keyword) ? keyword.trim() : null;
|
||||
return repository.pendingTasks(operatorId, warehouseId, normalizedKeyword).stream()
|
||||
@@ -267,11 +282,7 @@ public class LoadingPickService {
|
||||
if (replay != null) return replay;
|
||||
LoadingPickAssignmentEntity existing = repository.assignment(taskId);
|
||||
if (existing == null) {
|
||||
if (request.expectedAssignmentVersion() != 0
|
||||
|| repository.createAssignment(taskId, operatorId, operatorName, operatorId,
|
||||
operatorName, "CLAIMED") != 1) {
|
||||
throw conflict("PDA任务已被其他作业员领取");
|
||||
}
|
||||
throw conflict("PDA任务尚未指定作业员,请联系客服或仓管分配");
|
||||
} else {
|
||||
if (!Long.valueOf(operatorId).equals(existing.getAssigneeId())) {
|
||||
throw conflict("PDA任务已分配给其他作业员");
|
||||
@@ -289,12 +300,20 @@ public class LoadingPickService {
|
||||
public Task assign(long taskId, AssignRequest request, long operatorId, String operatorName) {
|
||||
inboundHoldGuard.requireTaskNotHeld(taskId);
|
||||
String operationId = operation(request.operationId());
|
||||
String assigneeName = request.assigneeName().trim();
|
||||
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
|
||||
var booking = repository.activeBookingByTask(taskId);
|
||||
var operatorResponse = domesticPdaOperators.required(internalToken, request.assigneeId(), booking == null ? 0L : booking.getWarehouseId());
|
||||
var assignee = operatorResponse == null ? null : operatorResponse.data();
|
||||
if (operatorResponse == null || !operatorResponse.successful() || assignee == null
|
||||
|| !request.assigneeId().equals(assignee.userId())
|
||||
|| !StringUtils.hasText(assignee.displayName())) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "所选国内头程 PDA 作业员无效");
|
||||
}
|
||||
String assigneeName = assignee.displayName().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);
|
||||
replay = replayTaskOperationForUpdate(taskId, operationId, requestHash, operatorId);
|
||||
if (replay != null) return replay;
|
||||
LoadingPickAssignmentEntity existing = repository.assignment(taskId);
|
||||
|
||||
Reference in New Issue
Block a user