提交
This commit is contained in:
+19
@@ -25,6 +25,25 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
@RequestMapping("/api/platform/v1/last-leg/relabel-applications")
|
||||
public class RelabelApplicationController {
|
||||
@Resource private RelabelApplicationService service;
|
||||
@Resource private com.baiyue.lastleg.relabel.pda.service.PdaRelabelService pdaService;
|
||||
|
||||
@PostMapping("/{relabelNo}/rework")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.RELABEL_ORDER_STATUS + "')")
|
||||
public ApiResponse<com.baiyue.lastleg.relabel.pda.dto.PdaRelabelResponses.Detail> rework(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String relabelNo,
|
||||
@Valid @RequestBody com.baiyue.lastleg.relabel.pda.dto.PdaRelabelRequests.Review request) {
|
||||
requirePlatform(user);
|
||||
return ApiResponse.success(pdaService.returnForRework(operator(user), user.username(), relabelNo, request));
|
||||
}
|
||||
|
||||
@GetMapping("/{relabelNo}/review-task")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.RELABEL_ORDER_LIST + "')")
|
||||
public ApiResponse<com.baiyue.lastleg.relabel.pda.dto.PdaRelabelResponses.Detail> reviewTask(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String relabelNo) {
|
||||
requirePlatform(user);
|
||||
var detail = service.detail(null, relabelNo);
|
||||
return ApiResponse.success(pdaService.detail(operator(user), Long.parseLong(detail.application().overseasWarehouseId()), relabelNo));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.RELABEL_ORDER_CREATE + "')")
|
||||
|
||||
@@ -23,7 +23,7 @@ public final class RelabelRequests {
|
||||
@NotNull LocalDateTime labelDeadline,
|
||||
@NotBlank @Size(max = 128) String contactName,
|
||||
@NotBlank @Size(max = 64) String contactPhone,
|
||||
@NotNull @DecimalMin(value = "0.01") BigDecimal chargeAmount,
|
||||
@DecimalMin(value = "0.01") BigDecimal chargeAmount,
|
||||
@Size(max = 1000) String remark,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@NotEmpty @Size(max = 500) List<@Valid Item> items,
|
||||
|
||||
+36
@@ -62,6 +62,33 @@ public class PdaRelabelController {
|
||||
identity.overseasWarehouseId(), relabelNo, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{relabelNo}/cartons/scan")
|
||||
public ApiResponse<PdaRelabelResponses.Detail> scanCarton(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String relabelNo, @Valid @RequestBody PdaRelabelRequests.CartonScan request) {
|
||||
Identity identity = identity(user);
|
||||
return ApiResponse.success(service.scanCarton(identity.operatorKey(), identity.operatorName(),
|
||||
identity.overseasWarehouseId(), relabelNo, request));
|
||||
}
|
||||
|
||||
@GetMapping("/{relabelNo}/documents")
|
||||
public ApiResponse<List<PdaRelabelService.Document>> documents(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String relabelNo) {
|
||||
return ApiResponse.success(service.documents(identity(user).overseasWarehouseId(), relabelNo));
|
||||
}
|
||||
|
||||
@GetMapping("/{relabelNo}/documents/{fileId}/download")
|
||||
public org.springframework.http.ResponseEntity<byte[]> download(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String relabelNo, @PathVariable long fileId,
|
||||
@org.springframework.web.bind.annotation.RequestParam String scope,
|
||||
@org.springframework.web.bind.annotation.RequestParam(required = false) Long itemId) {
|
||||
var file = service.downloadDocument(identity(user).overseasWarehouseId(), relabelNo, scope, itemId, fileId);
|
||||
return org.springframework.http.ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(file.contentType()))
|
||||
.header("Content-Disposition", org.springframework.http.ContentDisposition.inline()
|
||||
.filename(file.fileName(), java.nio.charset.StandardCharsets.UTF_8).build().toString())
|
||||
.body(file.content());
|
||||
}
|
||||
|
||||
@PostMapping("/{relabelNo}/units/skip")
|
||||
public ApiResponse<PdaRelabelResponses.Detail> skip(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String relabelNo, @Valid @RequestBody PdaRelabelRequests.Skip request) {
|
||||
@@ -86,6 +113,15 @@ public class PdaRelabelController {
|
||||
identity.userId(), relabelNo, file));
|
||||
}
|
||||
|
||||
@GetMapping("/{relabelNo}/photos/files/{fileId}/download")
|
||||
public org.springframework.http.ResponseEntity<byte[]> downloadWorkPhoto(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String relabelNo, @PathVariable long fileId) {
|
||||
Identity identity = identity(user);
|
||||
var file = service.downloadWorkPhoto(identity.operatorKey(), identity.overseasWarehouseId(), relabelNo, fileId);
|
||||
return org.springframework.http.ResponseEntity.ok().contentType(MediaType.parseMediaType(file.contentType()))
|
||||
.header("Cache-Control", "no-store").body(file.content());
|
||||
}
|
||||
|
||||
@PostMapping("/{relabelNo}/complete")
|
||||
public ApiResponse<PdaRelabelResponses.Detail> complete(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String relabelNo, @Valid @RequestBody PdaRelabelRequests.Complete request) {
|
||||
|
||||
+19
-2
@@ -1,6 +1,7 @@
|
||||
package com.baiyue.lastleg.relabel.pda.dto;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@@ -14,11 +15,23 @@ public final class PdaRelabelRequests {
|
||||
|
||||
public record Claim(@NotBlank @Size(max = 255) String idempotencyKey) { }
|
||||
|
||||
public record CartonScan(@NotBlank @Size(max = 255) String cartonMark,
|
||||
@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey) { }
|
||||
|
||||
public record Scan(@Positive Long itemId,
|
||||
@NotBlank @Size(max = 255) String oldLabel,
|
||||
@Size(max = 255) String newLabel,
|
||||
@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey) { }
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Valid List<SkuVerification> skuVerifications) {
|
||||
public Scan(Long itemId, String oldLabel, String newLabel, Integer expectedVersion, String idempotencyKey) {
|
||||
this(itemId, oldLabel, newLabel, expectedVersion, idempotencyKey, List.of());
|
||||
}
|
||||
}
|
||||
|
||||
public record SkuVerification(@NotBlank String skuCode, @NotBlank String newSkuCode,
|
||||
@Positive int quantity) { }
|
||||
|
||||
public record Skip(@NotNull @Positive Long itemId,
|
||||
@NotBlank @Size(max = 255) String oldLabel,
|
||||
@@ -36,7 +49,11 @@ public final class PdaRelabelRequests {
|
||||
public record Complete(@NotEmpty @Size(max = 20) List<@Positive Long> photoFileIds,
|
||||
@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Size(max = 1000) String remark) { }
|
||||
@Size(max = 1000) String remark, boolean manualCompletion) {
|
||||
public Complete(List<Long> photoFileIds, Integer expectedVersion, String idempotencyKey, String remark) {
|
||||
this(photoFileIds, expectedVersion, idempotencyKey, remark, false);
|
||||
}
|
||||
}
|
||||
|
||||
public record Review(@NotBlank @Size(max = 16) String decision,
|
||||
@NotNull @Min(0) Integer expectedVersion,
|
||||
|
||||
+3
-3
@@ -7,12 +7,12 @@ public final class PdaRelabelResponses {
|
||||
private PdaRelabelResponses() { }
|
||||
|
||||
public record Candidate(String relabelNo, String customerOrderNo, LocalDateTime labelDeadline,
|
||||
String applicationStatus, String taskStatus, int itemCount,
|
||||
String applicationStatus, String taskStatus, String operationType, int itemCount,
|
||||
int expectedPackages, int relabelledPackages, int skippedPackages,
|
||||
int exceptionPackages, int version, boolean claimedByCurrentOperator) { }
|
||||
|
||||
public record Detail(String relabelNo, String customerOrderNo, LocalDateTime labelDeadline,
|
||||
String contactName, String contactPhone, String applicationStatus,
|
||||
String contactName, String contactPhone, String applicationStatus, String operationType,
|
||||
String taskStatus, int expectedPackages, int relabelledPackages,
|
||||
int skippedPackages, int exceptionPackages, int version,
|
||||
String operatorName, String reviewerName, List<Long> completionPhotoFileIds,
|
||||
@@ -26,7 +26,7 @@ public final class PdaRelabelResponses {
|
||||
String cartonMark, String fbaCartonMark, String operationType,
|
||||
List<SkuItem> skuItems,
|
||||
int expectedPackages, int relabelledPackages, int skippedPackages,
|
||||
int exceptionPackages) { }
|
||||
int exceptionPackages, int cartonProcessedPackages, int skuProcessedPackages) { }
|
||||
public record SkuItem(String skuCode, String newSkuCode, int quantity, String remark) { }
|
||||
|
||||
public record UnitFact(Long factId, Long itemId, String oldLabel, String newLabel,
|
||||
|
||||
+3
@@ -15,6 +15,9 @@ public interface PdaRelabelTaskMapper extends BaseMapper<PdaRelabelTask> {
|
||||
@Select("SELECT * FROM ll_relabel_pda_task WHERE customer_id=#{customerId} AND application_id=#{applicationId} LIMIT 1 FOR UPDATE")
|
||||
PdaRelabelTask lock(@Param("customerId") String customerId, @Param("applicationId") Long applicationId);
|
||||
|
||||
@Select("SELECT * FROM ll_relabel_pda_task WHERE operator_key=#{operatorKey} AND status<>'COMPLETED' ORDER BY claimed_at ASC LIMIT 1 FOR UPDATE")
|
||||
PdaRelabelTask lockUnfinishedByOperator(@Param("operatorKey") String operatorKey);
|
||||
|
||||
@Update("""
|
||||
UPDATE ll_relabel_pda_task
|
||||
SET status=#{task.status}, operator_key=#{task.operatorKey}, operator_name=#{task.operatorName},
|
||||
|
||||
+193
-15
@@ -68,6 +68,8 @@ public class PdaRelabelService {
|
||||
@Resource private RelabelApplicationService applicationService;
|
||||
@Resource private RelabelPhotoFileGateway photoFiles;
|
||||
@Resource private ObjectMapper sourceObjectMapper;
|
||||
@Resource private com.baiyue.lastleg.mapper.LastlegOrderMapper tailOrders;
|
||||
@Resource private com.baiyue.lastleg.exceptionorder.service.ExceptionOrderService exceptionOrders;
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@PostConstruct
|
||||
@@ -81,7 +83,8 @@ public class PdaRelabelService {
|
||||
public List<PdaRelabelResponses.Candidate> candidates(String operatorKey, long overseasWarehouseId) {
|
||||
return applications.selectList(new LambdaQueryWrapper<RelabelApplication>()
|
||||
.eq(RelabelApplication::getOverseasWarehouseId, String.valueOf(overseasWarehouseId))
|
||||
.in(RelabelApplication::getStatus, RelabelApplicationService.PENDING_PROCESSING, PROCESSING)
|
||||
.in(RelabelApplication::getStatus, RelabelApplicationService.PENDING_PROCESSING, PROCESSING,
|
||||
RelabelApplicationService.CUSTOMER_CONFIRM_PENDING)
|
||||
.orderByAsc(RelabelApplication::getLabelDeadline)
|
||||
.orderByAsc(RelabelApplication::getCreatedAt))
|
||||
.stream().map(application -> candidate(application,
|
||||
@@ -106,6 +109,10 @@ public class PdaRelabelService {
|
||||
throw conflict("Relabel application is not executable");
|
||||
}
|
||||
PdaRelabelTask task = tasks.lock(application.getCustomerId(), application.getId());
|
||||
PdaRelabelTask activeTask = tasks.lockUnfinishedByOperator(operatorKey);
|
||||
if (activeTask != null && !Objects.equals(activeTask.getApplicationId(), application.getId())) {
|
||||
throw conflict("当前账号正在执行换标单 " + activeTask.getRelabelNo() + ",请先完成后再操作其他换标单");
|
||||
}
|
||||
if (task != null) {
|
||||
if (!operatorKey.equals(task.getOperatorKey()) || !PROCESSING.equals(task.getStatus())) {
|
||||
throw conflict("Relabel task is already claimed or awaiting review");
|
||||
@@ -146,10 +153,18 @@ public class PdaRelabelService {
|
||||
return unitAction(operatorKey, operatorName, overseasWarehouseId, relabelNo, "RELABEL", request.idempotencyKey(),
|
||||
request, itemId, request.oldLabel(), request.newLabel(), "RELABELLED", null, null,
|
||||
request.expectedVersion(), item -> {
|
||||
validateSkuVerification(item, request.skuVerifications());
|
||||
if (StringUtils.hasText(item.getCartonMark()) && !item.getCartonMark().trim().equalsIgnoreCase(request.oldLabel().trim())) {
|
||||
throw conflict("Scanned old carton mark does not match the relabel item");
|
||||
}
|
||||
String expectedFbaLabel = trim(item.getFbaCartonMark());
|
||||
if ("CARTON_LABEL".equals(operationType(item)) && StringUtils.hasText(expectedFbaLabel)
|
||||
&& !StringUtils.hasText(request.newLabel())) {
|
||||
throw conflict("请扫描新 FBA 箱唛确认箱唛换标");
|
||||
}
|
||||
if ("SKU_LABEL".equals(operationType(item)) && StringUtils.hasText(request.newLabel())) {
|
||||
throw conflict("SKU 换标不能修改 FBA 箱唛");
|
||||
}
|
||||
if (StringUtils.hasText(request.newLabel()) && StringUtils.hasText(expectedFbaLabel)
|
||||
&& !expectedFbaLabel.equalsIgnoreCase(request.newLabel().trim())) {
|
||||
throw conflict("Scanned new label does not match the approved FBA carton mark");
|
||||
@@ -160,6 +175,42 @@ public class PdaRelabelService {
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PdaRelabelResponses.Detail scanCarton(String operatorKey, String operatorName, long warehouseId,
|
||||
String relabelNo, PdaRelabelRequests.CartonScan request) {
|
||||
RelabelApplication application = requireApplication(relabelNo, warehouseId, false);
|
||||
RelabelItem item = requireItemByCartonMark(application, request.cartonMark());
|
||||
if ("SKU_LABEL".equals(operationType(item))) throw conflict("该箱为 SKU 换标,请进入 SKU 作业");
|
||||
if (!StringUtils.hasText(item.getFbaCartonMark())) throw conflict("该箱未维护对应 FBA 箱唛");
|
||||
return unitAction(operatorKey, operatorName, warehouseId, relabelNo, "SCAN_CARTON", request.idempotencyKey(),
|
||||
request, item.getId(), item.getCartonMark(), item.getFbaCartonMark(), "RELABELLED", null, null,
|
||||
request.expectedVersion(), current -> {
|
||||
if (!Objects.equals(current.getCartonMark(), item.getCartonMark())
|
||||
|| !Objects.equals(current.getFbaCartonMark(), item.getFbaCartonMark())) {
|
||||
throw conflict("箱唛绑定已变化,请重新扫描");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public record Document(String scope, String fileId, String fileName, Long itemId) { }
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Document> documents(long warehouseId, String relabelNo) {
|
||||
RelabelApplication app = requireApplication(relabelNo, warehouseId, false);
|
||||
List<Document> result = new ArrayList<>();
|
||||
if (StringUtils.hasText(app.getCartonTotalFileId())) result.add(new Document("CARTON_TOTAL", app.getCartonTotalFileId(), app.getCartonTotalFileName(), null));
|
||||
if (StringUtils.hasText(app.getSkuTotalFileId())) result.add(new Document("SKU_TOTAL", app.getSkuTotalFileId(), app.getSkuTotalFileName(), null));
|
||||
for (RelabelItem item : itemList(app)) if (StringUtils.hasText(item.getRelabelFileId()))
|
||||
result.add(new Document("ITEM", item.getRelabelFileId(), item.getRelabelFileName(), item.getId()));
|
||||
return result;
|
||||
}
|
||||
|
||||
public com.baiyue.lastleg.relabel.gateway.RelabelDocumentFileGateway.DownloadedFile downloadDocument(
|
||||
long warehouseId, String relabelNo, String scope, Long itemId, long fileId) {
|
||||
RelabelApplication app = requireApplication(relabelNo, warehouseId, false);
|
||||
return applicationService.downloadFile(app.getCustomerId(), relabelNo, scope, itemId, fileId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PdaRelabelResponses.Detail skip(String operatorKey, String operatorName, long overseasWarehouseId,
|
||||
String relabelNo, PdaRelabelRequests.Skip request) {
|
||||
@@ -194,6 +245,15 @@ public class PdaRelabelService {
|
||||
uploaded.contentType(), uploaded.sizeBytes());
|
||||
}
|
||||
|
||||
public RelabelPhotoFileGateway.DownloadedFile downloadWorkPhoto(String operatorKey, long warehouseId,
|
||||
String relabelNo, long fileId) {
|
||||
RelabelApplication application = requireApplication(relabelNo, warehouseId, false);
|
||||
PdaRelabelTask task = requireOwnedTask(application, operatorKey, false);
|
||||
if (fileId <= 0) throw notFound("作业照片不存在");
|
||||
photoFiles.validate(application.getCustomerId(), task.getId(), fileId);
|
||||
return photoFiles.download(application.getCustomerId(), task.getId(), fileId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PdaRelabelResponses.Detail complete(String operatorKey, String operatorName, long overseasWarehouseId,
|
||||
String relabelNo, PdaRelabelRequests.Complete request) {
|
||||
@@ -206,22 +266,59 @@ public class PdaRelabelService {
|
||||
requireVersion(task, request.expectedVersion());
|
||||
List<RelabelItem> detailItems = itemList(application);
|
||||
List<PdaRelabelUnitFact> facts = factList(task);
|
||||
boolean incomplete = detailItems.stream().anyMatch(item -> facts.stream()
|
||||
.filter(fact -> item.getId().equals(fact.getItemId())).count() < value(item.getNotifiedPackageCount()));
|
||||
if ((incomplete || request.manualCompletion()) && !StringUtils.hasText(request.remark()))
|
||||
throw conflict("手动完成必须填写仓库异常作业备注");
|
||||
if (incomplete && !request.manualCompletion()) throw conflict("仍有未处理箱,请选择手动完成并填写作业备注");
|
||||
if (request.photoFileIds() == null || request.photoFileIds().isEmpty() || request.photoFileIds().size() > 20) throw conflict("完成换标必须上传 1 至 20 张作业照片");
|
||||
if (request.manualCompletion() && request.remark().trim().length() > 900) throw conflict("手动作业备注不能超过 900 字");
|
||||
for (RelabelItem item : detailItems) {
|
||||
long actual = facts.stream().filter(fact -> item.getId().equals(fact.getItemId())).count();
|
||||
if (actual != value(item.getNotifiedPackageCount())) {
|
||||
if (actual > value(item.getNotifiedPackageCount())) {
|
||||
throw conflict("Every relabel item must have exactly one fact per notified package");
|
||||
}
|
||||
for (long i = actual; i < value(item.getNotifiedPackageCount()); i++) {
|
||||
PdaRelabelUnitFact fact = new PdaRelabelUnitFact();
|
||||
fact.setId(IdWorker.getId()); fact.setTaskId(task.getId()); fact.setApplicationId(application.getId());
|
||||
fact.setItemId(item.getId()); fact.setCustomerId(application.getCustomerId());
|
||||
fact.setResult("EXCEPTION"); fact.setExceptionCode("MANUAL_COMPLETE");
|
||||
fact.setReason(request.remark()); fact.setOperatorKey(operatorKey); fact.setOperatorName(operatorName);
|
||||
fact.setOccurredAt(LocalDateTime.now()); unitFacts.insert(fact);
|
||||
task.setExceptionPackages(value(task.getExceptionPackages()) + 1);
|
||||
}
|
||||
}
|
||||
List<Long> photoIds = request.photoFileIds().stream().distinct().toList();
|
||||
if (photoIds.size() != request.photoFileIds().size()) throw conflict("Relabel photo IDs must be unique");
|
||||
photoIds.forEach(fileId -> photoFiles.validate(application.getCustomerId(), task.getId(), fileId));
|
||||
if (request.manualCompletion()) {
|
||||
var relatedOrders = tailOrders.selectList(new LambdaQueryWrapper<com.baiyue.lastleg.entity.LastlegOrder>()
|
||||
.eq(com.baiyue.lastleg.entity.LastlegOrder::getCustomerId, application.getCustomerId())
|
||||
.eq(com.baiyue.lastleg.entity.LastlegOrder::getRelabelOrderNo, relabelNo));
|
||||
if (relatedOrders.isEmpty()) throw conflict("未找到关联尾程订单,无法登记异常完成");
|
||||
for (var order : relatedOrders) {
|
||||
String note = "换标单 " + relabelNo + " 手动完成:" + request.remark().trim();
|
||||
exceptionOrders.create(application.getCustomerId(), operatorKey, operatorName,
|
||||
new com.baiyue.lastleg.exceptionorder.dto.ExceptionOrderRequests.Create(
|
||||
application.getCustomerId(), null, order.getOrderNo(), "OTHER", note, null, null));
|
||||
tailOrders.update(null, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<com.baiyue.lastleg.entity.LastlegOrder>()
|
||||
.eq(com.baiyue.lastleg.entity.LastlegOrder::getId, order.getId())
|
||||
.eq(com.baiyue.lastleg.entity.LastlegOrder::getCustomerId, application.getCustomerId())
|
||||
.set(com.baiyue.lastleg.entity.LastlegOrder::getAbnormal, true)
|
||||
.set(com.baiyue.lastleg.entity.LastlegOrder::getWarehouseRemark,
|
||||
StringUtils.hasText(order.getWarehouseRemark()) ? order.getWarehouseRemark() + "\n" + note : note));
|
||||
}
|
||||
}
|
||||
String before = task.getStatus();
|
||||
task.setStatus(WAIT_REVIEW); task.setCompletionPhotoFileIds(write(photoIds));
|
||||
task.setCompletionRemark(trim(request.remark())); task.setSubmittedAt(LocalDateTime.now());
|
||||
updateTask(task, request.expectedVersion());
|
||||
insertHistory(task, "COMPLETE", before, WAIT_REVIEW, operatorKey, operatorName,
|
||||
Map.of("photoFileIds", photoIds, "remark", Objects.toString(trim(request.remark()), "")));
|
||||
application.setStatus(PROCESSING);
|
||||
Map.of("photoFileIds", photoIds, "remark", Objects.toString(trim(request.remark()), ""),
|
||||
"manualCompletion", request.manualCompletion()));
|
||||
applicationService.changeStatus(application.getCustomerId(), relabelNo, operatorKey,
|
||||
new RelabelRequests.ChangeStatus(RelabelApplicationService.CUSTOMER_CONFIRM_PENDING, request.remark()));
|
||||
application.setStatus(RelabelApplicationService.CUSTOMER_CONFIRM_PENDING);
|
||||
PdaRelabelResponses.Detail result = detail(application, task);
|
||||
saveOperation(task, operatorKey, "COMPLETE", request.idempotencyKey(), command, result);
|
||||
return result;
|
||||
@@ -230,6 +327,17 @@ public class PdaRelabelService {
|
||||
@Transactional
|
||||
public PdaRelabelResponses.Detail review(String operatorKey, String operatorName, long overseasWarehouseId,
|
||||
String relabelNo, PdaRelabelRequests.Review request) {
|
||||
throw conflict("复核由客户确认完成;需要重新贴标时请由平台填写备注退回");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PdaRelabelResponses.Detail returnForRework(String operatorKey, String operatorName, String relabelNo,
|
||||
PdaRelabelRequests.Review request) {
|
||||
if (!"REWORK".equals(request.decision()) || !StringUtils.hasText(request.remark()))
|
||||
throw badRequest("退回重新贴标必须填写原因");
|
||||
RelabelApplication scoped = applications.findAnyByRelabelNo(relabelNo);
|
||||
if (scoped == null) throw notFound("换标单不存在");
|
||||
long overseasWarehouseId = Long.parseLong(scoped.getOverseasWarehouseId());
|
||||
ReviewCommand command = new ReviewCommand(relabelNo, request);
|
||||
PdaRelabelResponses.Detail replay = replay(operatorKey, overseasWarehouseId, relabelNo,
|
||||
"REVIEW", request.idempotencyKey(), command);
|
||||
@@ -237,7 +345,8 @@ public class PdaRelabelService {
|
||||
String decision = request.decision().trim().toUpperCase(Locale.ROOT);
|
||||
if (!Set.of("APPROVE", "REWORK").contains(decision)) throw badRequest("Review decision must be APPROVE or REWORK");
|
||||
RelabelApplication application = requireApplication(relabelNo, overseasWarehouseId, true);
|
||||
if (!PROCESSING.equals(application.getStatus())) {
|
||||
if (!RelabelApplicationService.CUSTOMER_CONFIRM_PENDING.equals(application.getStatus())
|
||||
&& !PROCESSING.equals(application.getStatus())) {
|
||||
throw conflict("Relabel application is no longer executable");
|
||||
}
|
||||
PdaRelabelTask task = tasks.lock(application.getCustomerId(), application.getId());
|
||||
@@ -247,17 +356,26 @@ public class PdaRelabelService {
|
||||
}
|
||||
if (!WAIT_REVIEW.equals(task.getStatus())) throw conflict("Relabel task is not awaiting review");
|
||||
requireVersion(task, request.expectedVersion());
|
||||
// Preserve the previous round before freeing the active-label uniqueness constraint.
|
||||
List<PdaRelabelUnitFact> previousFacts = factList(task);
|
||||
insertHistory(task, "REWORK_ARCHIVE", WAIT_REVIEW, PROCESSING, operatorKey, operatorName,
|
||||
Map.of("facts", previousFacts, "photoFileIds", Objects.toString(task.getCompletionPhotoFileIds(), "[]"),
|
||||
"completionRemark", Objects.toString(task.getCompletionRemark(), ""), "remark", request.remark()));
|
||||
for (PdaRelabelUnitFact fact : previousFacts) {
|
||||
unitFacts.update(null, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<PdaRelabelUnitFact>()
|
||||
.eq(PdaRelabelUnitFact::getId, fact.getId()).eq(PdaRelabelUnitFact::getTaskId, task.getId())
|
||||
.set(PdaRelabelUnitFact::getResult, "REWORK_ARCHIVED").set(PdaRelabelUnitFact::getOldLabel, null));
|
||||
}
|
||||
task.setRelabelledPackages(0); task.setSkippedPackages(0); task.setExceptionPackages(0);
|
||||
task.setCompletionPhotoFileIds("[]"); task.setCompletionRemark(null); task.setSubmittedAt(null);
|
||||
applicationService.resetForRework(application.getCustomerId(), relabelNo, operatorKey, request.remark());
|
||||
application.setStatus(PROCESSING);
|
||||
String before = task.getStatus();
|
||||
String after = "APPROVE".equals(decision) ? COMPLETED : PROCESSING;
|
||||
String after = PROCESSING;
|
||||
task.setStatus(after); task.setReviewerKey(operatorKey); task.setReviewerName(operatorName);
|
||||
task.setReviewDecision(decision); task.setReviewRemark(trim(request.remark()));
|
||||
task.setReviewedAt(LocalDateTime.now());
|
||||
updateTask(task, request.expectedVersion());
|
||||
if ("APPROVE".equals(decision)) {
|
||||
applicationService.changeStatus(application.getCustomerId(), relabelNo, operatorKey,
|
||||
new RelabelRequests.ChangeStatus(RelabelApplicationService.CUSTOMER_CONFIRM_PENDING, request.remark()));
|
||||
application.setStatus(RelabelApplicationService.CUSTOMER_CONFIRM_PENDING);
|
||||
}
|
||||
insertHistory(task, "REVIEW_" + decision, before, after, operatorKey, operatorName,
|
||||
Map.of("decision", decision, "remark", Objects.toString(trim(request.remark()), "")));
|
||||
PdaRelabelResponses.Detail result = detail(application, task);
|
||||
@@ -278,6 +396,23 @@ public class PdaRelabelService {
|
||||
requireVersion(task, expectedVersion);
|
||||
RelabelItem item = requireItem(application, itemId);
|
||||
validation.accept(item);
|
||||
if ("CARTON_AND_SKU".equals(operationType(item)) && "RELABELLED".equals(resultCode)) {
|
||||
String stage = "SCAN_CARTON".equals(action) ? "CARTON" : "SKU";
|
||||
if (mixedStageDone(task, itemId, stage)) throw conflict("本箱该项作业已完成,请勿重复扫描");
|
||||
boolean otherDone = mixedStageDone(task, itemId, "CARTON".equals(stage) ? "SKU" : "CARTON");
|
||||
if (factList(task).stream().anyMatch(fact -> itemId.equals(fact.getItemId())))
|
||||
throw conflict("本箱已经处理完成");
|
||||
if (unitFacts.findOldLabel(trim(oldLabel)) != null) throw conflict("该仓库唛头已被其他作业处理");
|
||||
insertHistory(task, "MIXED_" + stage, PROCESSING, PROCESSING, operatorKey, operatorName,
|
||||
Map.of("itemId", itemId.toString(), "round", mixedRound(task), "oldLabel", oldLabel,
|
||||
"stage", stage, "request", command));
|
||||
if (!otherDone) {
|
||||
updateTask(task, expectedVersion);
|
||||
PdaRelabelResponses.Detail partial = detail(application, task);
|
||||
saveOperation(task, operatorKey, action, idempotencyKey, replayCommand, partial);
|
||||
return partial;
|
||||
}
|
||||
}
|
||||
List<PdaRelabelUnitFact> existingFacts = factList(task);
|
||||
long itemFacts = existingFacts.stream().filter(fact -> itemId.equals(fact.getItemId())).count();
|
||||
if (itemFacts >= value(item.getNotifiedPackageCount())) {
|
||||
@@ -299,7 +434,7 @@ public class PdaRelabelService {
|
||||
fact.setOperatorKey(operatorKey); fact.setOperatorName(operatorName); fact.setOccurredAt(LocalDateTime.now());
|
||||
try { unitFacts.insert(fact); }
|
||||
catch (DuplicateKeyException duplicate) { throw conflict("Old label was processed concurrently"); }
|
||||
if ("RELABELLED".equals(resultCode)) {
|
||||
if ("RELABELLED".equals(resultCode) && !"SKU_LABEL".equals(operationType(item))) {
|
||||
item.setNewCartonMark(resolvedNewLabel);
|
||||
item.setFbaCartonMark(resolvedNewLabel);
|
||||
items.updateById(item);
|
||||
@@ -395,16 +530,37 @@ public class PdaRelabelService {
|
||||
|
||||
private List<PdaRelabelUnitFact> factList(PdaRelabelTask task) {
|
||||
return unitFacts.selectList(new LambdaQueryWrapper<PdaRelabelUnitFact>()
|
||||
.ne(PdaRelabelUnitFact::getResult, "REWORK_ARCHIVED")
|
||||
.eq(PdaRelabelUnitFact::getCustomerId, task.getCustomerId())
|
||||
.eq(PdaRelabelUnitFact::getTaskId, task.getId())
|
||||
.orderByAsc(PdaRelabelUnitFact::getOccurredAt).orderByAsc(PdaRelabelUnitFact::getId));
|
||||
}
|
||||
|
||||
private String mixedRound(PdaRelabelTask task) {
|
||||
return Objects.toString(task.getReviewedAt(), "INITIAL");
|
||||
}
|
||||
|
||||
private boolean mixedStageDone(PdaRelabelTask task, Long itemId, String stage) {
|
||||
if (task == null) return false;
|
||||
return histories.selectList(new LambdaQueryWrapper<PdaRelabelHistory>()
|
||||
.eq(PdaRelabelHistory::getTaskId, task.getId())
|
||||
.eq(PdaRelabelHistory::getCustomerId, task.getCustomerId())
|
||||
.eq(PdaRelabelHistory::getAction, "MIXED_" + stage)).stream().anyMatch(history -> {
|
||||
if (!("MIXED_" + stage).equals(history.getAction())) return false;
|
||||
try {
|
||||
var data = objectMapper.readTree(history.getDetailJson());
|
||||
return itemId.toString().equals(data.path("itemId").asText())
|
||||
&& mixedRound(task).equals(data.path("round").asText());
|
||||
} catch (Exception invalid) { throw conflict("混合换标作业记录无法读取,请联系平台处理"); }
|
||||
});
|
||||
}
|
||||
|
||||
private PdaRelabelResponses.Candidate candidate(RelabelApplication application, PdaRelabelTask task,
|
||||
String operatorKey) {
|
||||
return new PdaRelabelResponses.Candidate(application.getRelabelNo(), application.getCustomerOrderNo(),
|
||||
application.getLabelDeadline(), application.getStatus(), task == null ? "AVAILABLE" : task.getStatus(),
|
||||
value(application.getItemCount()), task == null ? value(application.getNotifiedPackageCount()) : value(task.getExpectedPackages()),
|
||||
operationType(application), value(application.getItemCount()),
|
||||
task == null ? value(application.getNotifiedPackageCount()) : value(task.getExpectedPackages()),
|
||||
task == null ? 0 : value(task.getRelabelledPackages()), task == null ? 0 : value(task.getSkippedPackages()),
|
||||
task == null ? 0 : value(task.getExceptionPackages()), task == null ? 0 : value(task.getVersion()),
|
||||
task != null && operatorKey.equals(task.getOperatorKey()));
|
||||
@@ -422,7 +578,9 @@ public class PdaRelabelService {
|
||||
item.getRelabelFileId(), item.getRelabelFileName(), item.getRelabelFileUrl(),
|
||||
item.getCartonMark(), item.getFbaCartonMark(), operationType(item), readSkuItems(item.getSkuRelabelItemsJson()),
|
||||
value(item.getNotifiedPackageCount()), count(itemFacts, "RELABELLED"), count(itemFacts, "SKIPPED"),
|
||||
count(itemFacts, "EXCEPTION"));
|
||||
count(itemFacts, "EXCEPTION"),
|
||||
count(itemFacts, "RELABELLED") + (itemFacts.isEmpty() && mixedStageDone(task, item.getId(), "CARTON") ? 1 : 0),
|
||||
count(itemFacts, "RELABELLED") + (itemFacts.isEmpty() && mixedStageDone(task, item.getId(), "SKU") ? 1 : 0));
|
||||
}).toList();
|
||||
List<PdaRelabelResponses.UnitFact> factResponses = facts.stream().map(fact ->
|
||||
new PdaRelabelResponses.UnitFact(fact.getId(), fact.getItemId(), fact.getOldLabel(), fact.getNewLabel(),
|
||||
@@ -435,7 +593,7 @@ public class PdaRelabelService {
|
||||
history.getToStatus(), history.getOperatorName(), history.getDetailJson(), history.getCreatedAt())).toList();
|
||||
return new PdaRelabelResponses.Detail(application.getRelabelNo(), application.getCustomerOrderNo(),
|
||||
application.getLabelDeadline(), application.getContactName(), application.getContactPhone(),
|
||||
application.getStatus(), task == null ? "AVAILABLE" : task.getStatus(),
|
||||
application.getStatus(), operationType(application), task == null ? "AVAILABLE" : task.getStatus(),
|
||||
task == null ? value(application.getNotifiedPackageCount()) : value(task.getExpectedPackages()),
|
||||
task == null ? 0 : value(task.getRelabelledPackages()), task == null ? 0 : value(task.getSkippedPackages()),
|
||||
task == null ? 0 : value(task.getExceptionPackages()), task == null ? 0 : value(task.getVersion()),
|
||||
@@ -514,6 +672,26 @@ public class PdaRelabelService {
|
||||
return StringUtils.hasText(item.getOperationType()) ? item.getOperationType() : "CARTON_LABEL";
|
||||
}
|
||||
|
||||
private String operationType(RelabelApplication application) {
|
||||
List<RelabelItem> items = itemList(application);
|
||||
boolean sku = items.stream().anyMatch(item -> Set.of("SKU_LABEL", "CARTON_AND_SKU").contains(operationType(item)));
|
||||
boolean carton = items.stream().anyMatch(item -> !"SKU_LABEL".equals(operationType(item)));
|
||||
return sku && carton ? "CARTON_AND_SKU" : sku ? "SKU_LABEL" : "CARTON_LABEL";
|
||||
}
|
||||
|
||||
private void validateSkuVerification(RelabelItem item, List<PdaRelabelRequests.SkuVerification> scanned) {
|
||||
if (!Set.of("SKU_LABEL", "CARTON_AND_SKU").contains(operationType(item))) return;
|
||||
List<PdaRelabelResponses.SkuItem> expected = readSkuItems(item.getSkuRelabelItemsJson());
|
||||
if (expected.isEmpty()) throw conflict("该箱缺少 SKU 换标及数量明细,请补齐后作业");
|
||||
List<PdaRelabelRequests.SkuVerification> remaining = new java.util.ArrayList<>(scanned == null ? List.of() : scanned);
|
||||
if (remaining.size() != expected.size()) throw conflict("请完成该箱全部 SKU 换标核对");
|
||||
for (PdaRelabelResponses.SkuItem sku : expected) {
|
||||
boolean matched = remaining.remove(new PdaRelabelRequests.SkuVerification(
|
||||
sku.skuCode(), sku.newSkuCode(), sku.quantity()));
|
||||
if (!matched) throw conflict("扫描的旧 SKU、新 SKU 或数量与换标明细不一致");
|
||||
}
|
||||
}
|
||||
|
||||
private static int count(List<PdaRelabelUnitFact> facts, String result) {
|
||||
return (int) facts.stream().filter(fact -> result.equals(fact.getResult())).count();
|
||||
}
|
||||
|
||||
+62
-23
@@ -180,7 +180,15 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
if (!String.valueOf(fileId).equals(item.getRelabelFileId())) throw notFound("换标资料未绑定当前明细");
|
||||
return documentFiles.download(application.getCustomerId(), "OVERSEAS_RELABEL_ITEM_DOCUMENT", String.valueOf(item.getId()), fileId);
|
||||
}
|
||||
if (!"TOTAL".equalsIgnoreCase(scope) || itemId != null) throw badRequest("资料范围必须为 TOTAL 或 ITEM,总资料不指定明细");
|
||||
if (itemId == null && ("CARTON_TOTAL".equalsIgnoreCase(scope) || "TOTAL".equalsIgnoreCase(scope))
|
||||
&& String.valueOf(fileId).equals(application.getCartonTotalFileId())) {
|
||||
return documentFiles.download(application.getCustomerId(), "OVERSEAS_RELABEL_CARTON_TOTAL_DOCUMENT", String.valueOf(application.getId()), fileId);
|
||||
}
|
||||
if (itemId == null && ("SKU_TOTAL".equalsIgnoreCase(scope) || "TOTAL".equalsIgnoreCase(scope))
|
||||
&& String.valueOf(fileId).equals(application.getSkuTotalFileId())) {
|
||||
return documentFiles.download(application.getCustomerId(), "OVERSEAS_RELABEL_SKU_TOTAL_DOCUMENT", String.valueOf(application.getId()), fileId);
|
||||
}
|
||||
if (!"TOTAL".equalsIgnoreCase(scope) || itemId != null) throw badRequest("资料未绑定当前申请单");
|
||||
if (!String.valueOf(fileId).equals(application.getTotalFileId())) throw notFound("换标资料未绑定当前申请单");
|
||||
return documentFiles.download(application.getCustomerId(), "OVERSEAS_RELABEL_TOTAL_DOCUMENT", String.valueOf(application.getId()), fileId);
|
||||
}
|
||||
@@ -323,6 +331,8 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
long applicationId = IdWorker.getId();
|
||||
String operationWarehouseId = request.operationWarehouseId().trim();
|
||||
String relabelNo = orderNoGenerator.next("RELABEL", operationWarehouseId, applicationId);
|
||||
BigDecimal chargeAmount = Objects.requireNonNullElse(request.chargeAmount(), BigDecimal.ZERO);
|
||||
boolean requiresWallet = chargeAmount.signum() > 0;
|
||||
int holdIndex = 0;
|
||||
for (SourceInventory source : sources) {
|
||||
inventoryCommands.hold(customerId, operatorId, new InventoryCommandRequests.Move(
|
||||
@@ -352,9 +362,9 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
application.setItemCount(request.items().size());
|
||||
application.setTotalPackageCount(total);
|
||||
application.setNotifiedPackageCount(notified);
|
||||
application.setChargeAmount(request.chargeAmount());
|
||||
application.setChargeAmount(chargeAmount);
|
||||
application.setChargeCurrency("CNY");
|
||||
application.setWalletStatus("FREEZE_PENDING");
|
||||
application.setWalletStatus(requiresWallet ? "FREEZE_PENDING" : "NOT_REQUIRED");
|
||||
application.setIdempotencyKey(request.idempotencyKey().trim());
|
||||
application.setRequestHash(requestHash);
|
||||
application.setCreatedBy(trim(operatorId));
|
||||
@@ -362,10 +372,12 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
application.setUpdatedAt(now);
|
||||
applications.insert(application);
|
||||
|
||||
String walletEventId = walletCommands.publish("FREEZE", Long.parseLong(customerId), "RELABEL_ORDER",
|
||||
relabelNo, request.chargeAmount(), "换标订单费用冻结", 1L);
|
||||
application.setWalletCommandEventId(walletEventId);
|
||||
applications.updateById(application);
|
||||
if (requiresWallet) {
|
||||
String walletEventId = walletCommands.publish("FREEZE", Long.parseLong(customerId), "RELABEL_ORDER",
|
||||
relabelNo, chargeAmount, "换标订单费用冻结", 1L);
|
||||
application.setWalletCommandEventId(walletEventId);
|
||||
applications.updateById(application);
|
||||
}
|
||||
|
||||
for (SourceInventory source : sources) {
|
||||
insertItem(customerId, applicationId, source, now);
|
||||
@@ -395,7 +407,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
query.skuCode())
|
||||
.orderByDesc(RelabelApplication::getCreatedAt);
|
||||
if (PENDING_PROCESSING.equals(requestedStatus)) {
|
||||
wrapper.in(RelabelApplication::getStatus, PENDING_PROCESSING, "PROCESSING", CUSTOMER_CONFIRM_PENDING,
|
||||
wrapper.in(RelabelApplication::getStatus, PENDING_PROCESSING, CUSTOMER_CONFIRM_PENDING,
|
||||
"REJECTED", "CANCELLED");
|
||||
} else if ("COMPLETED".equals(requestedStatus)) {
|
||||
wrapper.in(RelabelApplication::getStatus, READY_FOR_OUTBOUND, "COMPLETED");
|
||||
@@ -442,7 +454,8 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
if (!STATUSES.contains(next)) throw badRequest("Unsupported relabel status");
|
||||
if (next.equals(application.getStatus())) return detail(customerId, relabelNo);
|
||||
validateTransition(application.getStatus(), next);
|
||||
if ("PROCESSING".equals(next) && !"FROZEN".equals(application.getWalletStatus())) {
|
||||
if ("PROCESSING".equals(next)
|
||||
&& !Set.of("FROZEN", "NOT_REQUIRED").contains(application.getWalletStatus())) {
|
||||
throw conflict("Relabel funds must be frozen before processing");
|
||||
}
|
||||
String before = application.getStatus();
|
||||
@@ -462,20 +475,22 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
&& !preDispatched) {
|
||||
releaseInventory(customerId, operatorId, application);
|
||||
}
|
||||
BigDecimal quantity = BigDecimal.valueOf(Objects.requireNonNullElse(application.getTotalPackageCount(), 0));
|
||||
feeSources.publishApproved(operatorId, "RELABEL:" + relabelNo,
|
||||
new OverseasFeeSourceDtos.SaveRequest(customerId, linkedOrders.get(0).getCustomerName(),
|
||||
"OVERSEAS_RELABEL_FEE", "RELABEL_ORDER", application.getId(), relabelNo,
|
||||
application.getLastlegOrderId(), null, null, null, null, null,
|
||||
parseLong(application.getOverseasWarehouseId()), "RELABEL_SERVICE", "换标费", "RECEIVABLE",
|
||||
null, null, null, null, null, null, null, List.of(), LocalDate.now(),
|
||||
application.getChargeCurrency(), application.getChargeAmount(),
|
||||
quantity.signum() > 0 ? quantity : null, quantity.signum() > 0 ? "BOX" : null,
|
||||
quantity.signum() > 0 ? application.getChargeAmount().divide(quantity, 6, java.math.RoundingMode.HALF_UP) : null,
|
||||
null, null));
|
||||
walletCommands.publish("SETTLE", Long.parseLong(customerId), "RELABEL_ORDER", relabelNo, null,
|
||||
"换标完成,结算费用", 2L);
|
||||
application.setWalletStatus("SETTLE_PENDING"); applications.updateById(application);
|
||||
if (!"NOT_REQUIRED".equals(application.getWalletStatus())) {
|
||||
BigDecimal quantity = BigDecimal.valueOf(Objects.requireNonNullElse(application.getTotalPackageCount(), 0));
|
||||
feeSources.publishApproved(operatorId, "RELABEL:" + relabelNo,
|
||||
new OverseasFeeSourceDtos.SaveRequest(customerId, linkedOrders.get(0).getCustomerName(),
|
||||
"OVERSEAS_RELABEL_FEE", "RELABEL_ORDER", application.getId(), relabelNo,
|
||||
application.getLastlegOrderId(), null, null, null, null, null,
|
||||
parseLong(application.getOverseasWarehouseId()), "RELABEL_SERVICE", "换标费", "RECEIVABLE",
|
||||
null, null, null, null, null, null, null, List.of(), LocalDate.now(),
|
||||
application.getChargeCurrency(), application.getChargeAmount(),
|
||||
quantity.signum() > 0 ? quantity : null, quantity.signum() > 0 ? "BOX" : null,
|
||||
quantity.signum() > 0 ? application.getChargeAmount().divide(quantity, 6, java.math.RoundingMode.HALF_UP) : null,
|
||||
null, null));
|
||||
walletCommands.publish("SETTLE", Long.parseLong(customerId), "RELABEL_ORDER", relabelNo, null,
|
||||
"换标完成,结算费用", 2L);
|
||||
application.setWalletStatus("SETTLE_PENDING"); applications.updateById(application);
|
||||
}
|
||||
} else if (Set.of("REJECTED", "CANCELLED").contains(next)) {
|
||||
releaseInventory(customerId, operatorId, application);
|
||||
if ("FROZEN".equals(application.getWalletStatus())) {
|
||||
@@ -563,6 +578,21 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
return detail(customerId, relabelNo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void resetForRework(String customerId, String relabelNo, String operatorId, String remark) {
|
||||
RelabelApplication application = applications.lockNo(customerId, relabelNo);
|
||||
if (application == null || !Set.of(CUSTOMER_CONFIRM_PENDING, "PROCESSING").contains(application.getStatus())
|
||||
|| application.getCustomerConfirmedAt() != null) throw conflict("客户已确认或当前状态不允许退回");
|
||||
String before = application.getStatus();
|
||||
application.setStatus("PROCESSING"); application.setFulfillmentStatus("WAIT_RELABEL");
|
||||
application.setUpdatedAt(LocalDateTime.now()); applications.updateById(application);
|
||||
for (LastlegOrder order : tailOrders(application)) {
|
||||
order.setRelabelStatus("PROCESSING"); order.setSubOrderStatus("RELABEL:PROCESSING");
|
||||
order.setUpdatedAt(LocalDateTime.now()); orders.updateById(order);
|
||||
}
|
||||
insertHistory(customerId, application.getId(), before, "PROCESSING", operatorId, remark, LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public RelabelResponses.Detail confirmCompletion(String customerId, String relabelNo, String operatorId,
|
||||
RelabelRequests.CustomerConfirm request) {
|
||||
@@ -573,6 +603,14 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
if (!CUSTOMER_CONFIRM_PENDING.equals(application.getStatus())) {
|
||||
throw conflict("Relabel order is not awaiting customer confirmation");
|
||||
}
|
||||
PdaRelabelTask reviewTask = pdaTasks.lock(customerId, application.getId());
|
||||
if (reviewTask == null || !Set.of("WAIT_REVIEW", "COMPLETED").contains(reviewTask.getStatus()))
|
||||
throw conflict("仓库尚未提交换标完工复核");
|
||||
reviewTask.setStatus("COMPLETED"); reviewTask.setReviewDecision("CUSTOMER_CONFIRMED");
|
||||
reviewTask.setReviewerKey(operatorId); reviewTask.setReviewerName("客户确认");
|
||||
reviewTask.setReviewRemark(request.remark()); reviewTask.setReviewedAt(LocalDateTime.now());
|
||||
reviewTask.setUpdatedAt(LocalDateTime.now());
|
||||
if (pdaTasks.updateWithVersion(reviewTask, reviewTask.getVersion()) != 1) throw conflict("复核状态已变化,请刷新");
|
||||
if (Boolean.TRUE.equals(application.getPrintShippingLabel())
|
||||
&& !StringUtils.hasText(application.getShippingLabelFileId())) {
|
||||
throw conflict("Upload the shipping label before confirming relabel completion");
|
||||
@@ -899,6 +937,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
}
|
||||
|
||||
private static String businessRelabelStatus(String workflowStatus) {
|
||||
if ("PROCESSING".equals(workflowStatus)) return "PROCESSING";
|
||||
return Set.of(READY_FOR_OUTBOUND, "COMPLETED").contains(workflowStatus)
|
||||
? "COMPLETED" : PENDING_PROCESSING;
|
||||
}
|
||||
|
||||
@@ -641,6 +641,7 @@ public class LastlegOrderService {
|
||||
}
|
||||
|
||||
private static String relabelBusinessStatus(String workflowStatus) {
|
||||
if ("PROCESSING".equals(workflowStatus)) return "PROCESSING";
|
||||
if (!StringUtils.hasText(workflowStatus) || "NOT_REQUIRED".equals(workflowStatus)) return workflowStatus;
|
||||
return Set.of("READY_FOR_OUTBOUND", "COMPLETED").contains(workflowStatus)
|
||||
? "COMPLETED" : "PENDING_PROCESSING";
|
||||
|
||||
+1
@@ -34,6 +34,7 @@ public final class WarehouseTransferResponses {
|
||||
int packageCount, String deliveryMethod, LocalDateTime estimatedArrivalAt,
|
||||
LocalDateTime estimatedShipAt,
|
||||
String fbaShipmentNo, String trackingNo, String fbaBoxNos, String remark,
|
||||
String reviewDecision, String reviewRemark, String reviewedBy, LocalDateTime reviewedAt,
|
||||
List<SourceOrder> sourceOrders, List<Item> items, List<Carton> cartons,
|
||||
List<Pallet> pallets, List<RelabelBinding> relabelBindings,
|
||||
List<Allocation> allocations, LocalDateTime customerConfirmedAt,
|
||||
|
||||
+4
@@ -37,6 +37,10 @@ public class WarehouseTransferOrder {
|
||||
private String relabelNo;
|
||||
private LocalDateTime relabelCompletedAt;
|
||||
private String status;
|
||||
private String reviewDecision;
|
||||
private String reviewRemark;
|
||||
private String reviewedBy;
|
||||
private LocalDateTime reviewedAt;
|
||||
private String allocationIdempotencyKey;
|
||||
private String allocationRequestHash;
|
||||
private String outboundIdempotencyKey;
|
||||
|
||||
+9
-2
@@ -323,7 +323,8 @@ public class WarehouseTransferService {
|
||||
transfer.getOperatingWarehouseId(), transfer.getFbaWarehouseCode(), transfer.getDeliveryAddress(),
|
||||
transfer.getPackageCount(), transfer.getDeliveryMethod(), transfer.getEstimatedArrivalAt(),
|
||||
transfer.getEstimatedShipAt(), transfer.getFbaShipmentNo(), transfer.getTrackingNo(), transfer.getFbaBoxNos(),
|
||||
transfer.getRemark(), sources, items, cartons, pallets, bindings, allocations,
|
||||
transfer.getRemark(), transfer.getReviewDecision(), transfer.getReviewRemark(), transfer.getReviewedBy(),
|
||||
transfer.getReviewedAt(), sources, items, cartons, pallets, bindings, allocations,
|
||||
transfer.getCustomerConfirmedAt(), transfer.getOutboundAt(), transfer.getCreatedAt());
|
||||
}
|
||||
|
||||
@@ -375,7 +376,13 @@ public class WarehouseTransferService {
|
||||
String before = order.getCurrentStatus();
|
||||
boolean incomingTransit = transfer.getTransferNo().equals(transfer.getInboundCode());
|
||||
String transferStatus = APPROVED.equals(next) && incomingTransit ? WAIT_PDA_RECEIVING : next;
|
||||
transfer.setStatus(transferStatus); transfer.setUpdatedAt(now); transfers.updateById(transfer);
|
||||
transfer.setStatus(transferStatus);
|
||||
transfer.setReviewDecision(next);
|
||||
transfer.setReviewRemark(trim(request.remark()));
|
||||
transfer.setReviewedBy(operatorId);
|
||||
transfer.setReviewedAt(now);
|
||||
transfer.setUpdatedAt(now);
|
||||
transfers.updateById(transfer);
|
||||
String orderStatus = APPROVED.equals(next)
|
||||
? (incomingTransit
|
||||
? ("WAREHOUSE_PICKUP".equals(transfer.getDeliveryMethod()) ? "WAIT_PICKUP" : "WAIT_DELIVERY")
|
||||
|
||||
Reference in New Issue
Block a user