fix: close phase4 overseas acceptance gaps

This commit is contained in:
2026-08-24 21:00:22 +08:00
parent 005c3f54e2
commit 3607bebbfd
42 changed files with 860 additions and 203 deletions
@@ -34,11 +34,11 @@ public class ReceivingContainerController {
@Resource private ReceivingContainerService service;
@GetMapping("/candidates")
@Operation(summary = "List pending receiving containers for the authenticated customer")
@Operation(summary = "List pending receiving containers across customers for the authenticated warehouse picker")
public ApiResponse<List<ReceivingContainerResponses.Candidate>> candidates(
@AuthenticationPrincipal AuthenticatedUser user) {
Identity identity = identity(user);
return ApiResponse.success(service.candidates(identity.customerId(), identity.operatorKey()));
return ApiResponse.success(service.candidates(identity.operatorKey()));
}
@GetMapping("/{containerNo}")
@@ -46,14 +46,14 @@ public class ReceivingContainerController {
public ApiResponse<ReceivingContainerResponses.Session> detail(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String containerNo) {
Identity identity = identity(user);
return ApiResponse.success(service.detail(identity.customerId(), identity.operatorKey(), containerNo));
return ApiResponse.success(service.detail(identity.operatorKey(), containerNo));
}
@PostMapping("/{containerNo}/prepare")
public ApiResponse<ReceivingContainerResponses.Session> prepare(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String containerNo, @Valid @RequestBody ReceivingContainerRequests.Action request) {
Identity identity = identity(user);
return ApiResponse.success(service.prepare(identity.customerId(), identity.operatorKey(), identity.operatorName(),
return ApiResponse.success(service.prepare(identity.operatorKey(), identity.operatorName(),
containerNo, request));
}
@@ -62,20 +62,20 @@ public class ReceivingContainerController {
@RequestPart("file") MultipartFile file) {
Identity identity = identity(user);
if (user.userId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "PDA user ID is required");
return ApiResponse.success(service.uploadPhoto(identity.customerId(), identity.operatorKey(), user.userId(), file));
return ApiResponse.success(service.uploadPhoto(identity.operatorKey(), user.userId(), file));
}
@PostMapping("/current/sign")
public ApiResponse<ReceivingContainerResponses.Session> sign(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ReceivingContainerRequests.Sign request) {
Identity identity = identity(user);
return ApiResponse.success(service.sign(identity.customerId(), identity.operatorKey(), identity.operatorName(), request));
return ApiResponse.success(service.sign(identity.operatorKey(), identity.operatorName(), request));
}
@GetMapping("/current")
public ApiResponse<ReceivingContainerResponses.Session> current(@AuthenticationPrincipal AuthenticatedUser user) {
Identity identity = identity(user);
return ApiResponse.success(service.current(identity.customerId(), identity.operatorKey()));
return ApiResponse.success(service.current(identity.operatorKey()));
}
@PostMapping("/current/cartons/resolve")
@@ -83,7 +83,7 @@ public class ReceivingContainerController {
@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ReceivingContainerRequests.CartonResolve request) {
Identity identity = identity(user);
return ApiResponse.success(service.resolveCarton(identity.customerId(), identity.operatorKey(), request));
return ApiResponse.success(service.resolveCarton(identity.operatorKey(), request));
}
@PostMapping("/current/skus/resolve")
@@ -91,7 +91,7 @@ public class ReceivingContainerController {
@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ReceivingContainerRequests.SkuResolve request) {
Identity identity = identity(user);
return ApiResponse.success(service.resolveSku(identity.customerId(), identity.operatorKey(), request));
return ApiResponse.success(service.resolveSku(identity.operatorKey(), request));
}
@PostMapping("/current/pallets/resolve")
@@ -99,41 +99,42 @@ public class ReceivingContainerController {
@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ReceivingContainerRequests.PalletResolve request) {
Identity identity = identity(user);
return ApiResponse.success(service.resolvePallet(identity.customerId(), identity.operatorKey(), request));
return ApiResponse.success(service.resolvePallet(identity.operatorKey(), request));
}
@PostMapping("/current/receipts/bulk")
public ApiResponse<ReceivingContainerResponses.Receipt> receiveBulk(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ReceivingContainerRequests.BulkReceive request) {
Identity identity = identity(user);
return ApiResponse.success(service.receiveBulk(identity.customerId(), identity.operatorKey(), identity.operatorName(), request));
return ApiResponse.success(service.receiveBulk(identity.operatorKey(), identity.operatorName(), request));
}
@PostMapping("/current/receipts/carton")
public ApiResponse<ReceivingContainerResponses.Receipt> receiveCarton(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ReceivingContainerRequests.CartonReceive request) {
Identity identity = identity(user);
return ApiResponse.success(service.receiveCarton(identity.customerId(), identity.operatorKey(), identity.operatorName(), request));
return ApiResponse.success(service.receiveCarton(identity.operatorKey(), identity.operatorName(), request));
}
@PostMapping("/current/complete")
public ApiResponse<ReceivingContainerResponses.Session> complete(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ReceivingContainerRequests.Action request) {
Identity identity = identity(user);
return ApiResponse.success(service.complete(identity.customerId(), identity.operatorKey(), request));
return ApiResponse.success(service.complete(identity.operatorKey(), request));
}
private static Identity identity(AuthenticatedUser user) {
if (user == null || user.tenantId() == null || user.tenantId() <= 0 || user.userId() == null) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Authenticated PDA customer identity is required");
if (user == null || user.userId() == null || user.tenantId() == null || user.tenantId() != 0
|| !"PLATFORM".equals(user.appCode())) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Authenticated platform warehouse PDA identity is required");
}
if (user.permissions() == null || !user.permissions().contains(OPERATE_PERMISSION)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, OPERATE_PERMISSION + " permission is required");
}
String operator = String.valueOf(user.userId());
return new Identity(String.valueOf(user.tenantId()), operator,
return new Identity(operator,
user.username() == null || user.username().isBlank() ? operator : user.username());
}
private record Identity(String customerId, String operatorKey, String operatorName) { }
private record Identity(String operatorKey, String operatorName) { }
}
@@ -8,9 +8,8 @@ import org.apache.ibatis.annotations.Select;
@Mapper
public interface ReceivingContainerOperationMapper extends BaseMapper<ReceivingContainerOperation> {
@Select("SELECT * FROM ll_receiving_container_operation WHERE customer_id=#{customerId} " +
"AND operator_key=#{operatorKey} AND idempotency_key=#{key} LIMIT 1")
ReceivingContainerOperation find(@Param("customerId") String customerId,
@Param("operatorKey") String operatorKey,
@Select("SELECT * FROM ll_receiving_container_operation " +
"WHERE operator_key=#{operatorKey} AND idempotency_key=#{key} LIMIT 1")
ReceivingContainerOperation find(@Param("operatorKey") String operatorKey,
@Param("key") String key);
}
@@ -8,18 +8,15 @@ import org.apache.ibatis.annotations.Select;
@Mapper
public interface ReceivingContainerSessionMapper extends BaseMapper<ReceivingContainerSession> {
@Select("SELECT * FROM ll_receiving_container_session WHERE customer_id=#{customerId} " +
"AND active_operator_key=#{activeOperatorKey} LIMIT 1 FOR UPDATE")
ReceivingContainerSession lockActiveByOperator(@Param("customerId") String customerId,
@Param("activeOperatorKey") String activeOperatorKey);
@Select("SELECT * FROM ll_receiving_container_session " +
"WHERE active_operator_key=#{activeOperatorKey} LIMIT 1 FOR UPDATE")
ReceivingContainerSession lockActiveByOperator(@Param("activeOperatorKey") String activeOperatorKey);
@Select("SELECT * FROM ll_receiving_container_session WHERE customer_id=#{customerId} " +
"AND active_operator_key=#{activeOperatorKey} LIMIT 1")
ReceivingContainerSession findActiveByOperator(@Param("customerId") String customerId,
@Param("activeOperatorKey") String activeOperatorKey);
@Select("SELECT * FROM ll_receiving_container_session " +
"WHERE active_operator_key=#{activeOperatorKey} LIMIT 1")
ReceivingContainerSession findActiveByOperator(@Param("activeOperatorKey") String activeOperatorKey);
@Select("SELECT * FROM ll_receiving_container_session WHERE customer_id=#{customerId} " +
"AND active_container_key=#{activeContainerKey} LIMIT 1 FOR UPDATE")
ReceivingContainerSession lockActiveByContainer(@Param("customerId") String customerId,
@Param("activeContainerKey") String activeContainerKey);
@Select("SELECT * FROM ll_receiving_container_session " +
"WHERE active_container_key=#{activeContainerKey} LIMIT 1 FOR UPDATE")
ReceivingContainerSession lockActiveByContainer(@Param("activeContainerKey") String activeContainerKey);
}
@@ -80,16 +80,17 @@ public class ReceivingContainerService {
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
}
public List<ReceivingContainerResponses.Candidate> candidates(String customerId, String operatorKey) {
ReceivingContainerSession current = sessions.findActiveByOperator(customerId, activeOperatorKey(customerId, operatorKey));
public List<ReceivingContainerResponses.Candidate> candidates(String operatorKey) {
ReceivingContainerSession current = sessions.findActiveByOperator(activeOperatorKey(operatorKey));
Map<String, List<InboundPlan>> grouped = new LinkedHashMap<>();
plans.selectList(new LambdaQueryWrapper<InboundPlan>().eq(InboundPlan::getCustomerId, customerId)
plans.selectList(new LambdaQueryWrapper<InboundPlan>()
.ne(InboundPlan::getStatus, "COMPLETED").orderByAsc(InboundPlan::getCreatedAt)).forEach(plan -> {
if (plan.getContainerNo() != null && !plan.getContainerNo().isBlank()) {
grouped.computeIfAbsent(plan.getContainerNo(), ignored -> new ArrayList<>()).add(plan);
}
});
return grouped.entrySet().stream().map(entry -> {
requireSingleCustomer(entry.getValue(), entry.getKey());
Totals totals = totals(entry.getValue());
boolean active = current != null && entry.getKey().equals(current.getContainerNo());
return new ReceivingContainerResponses.Candidate(entry.getKey(), active ? current.getStatus() : "WAIT_SIGN",
@@ -98,9 +99,10 @@ public class ReceivingContainerService {
}).toList();
}
public ReceivingContainerResponses.Session detail(String customerId, String operatorKey, String containerNo) {
List<InboundPlan> containerPlans = requirePlans(customerId, containerNo);
ReceivingContainerSession current = sessions.findActiveByOperator(customerId, activeOperatorKey(customerId, operatorKey));
public ReceivingContainerResponses.Session detail(String operatorKey, String containerNo) {
List<InboundPlan> containerPlans = requirePlans(containerNo);
String customerId = requireSingleCustomer(containerPlans, containerNo);
ReceivingContainerSession current = sessions.findActiveByOperator(activeOperatorKey(operatorKey));
if (current != null && !containerNo.equals(current.getContainerNo())) {
throw conflict("Current account is locked to another selected container");
}
@@ -109,27 +111,28 @@ public class ReceivingContainerService {
}
@Transactional
public ReceivingContainerResponses.Session prepare(String customerId, String operatorKey, String operatorName,
String containerNo, ReceivingContainerRequests.Action request) {
public ReceivingContainerResponses.Session prepare(String operatorKey, String operatorName, String containerNo,
ReceivingContainerRequests.Action request) {
PrepareCommand command = new PrepareCommand(containerNo, request.idempotencyKey());
ReceivingContainerOperation replay = replay(customerId, operatorKey, "PREPARE", request.idempotencyKey(), command);
ReceivingContainerOperation replay = replay(operatorKey, "PREPARE", request.idempotencyKey(), command);
if (replay != null) return read(replay, ReceivingContainerResponses.Session.class);
List<InboundPlan> containerPlans = requirePlans(customerId, containerNo);
String activeOperatorKey = activeOperatorKey(customerId, operatorKey);
ReceivingContainerSession active = sessions.lockActiveByOperator(customerId, activeOperatorKey);
List<InboundPlan> containerPlans = requirePlans(containerNo);
String customerId = requireSingleCustomer(containerPlans, containerNo);
String activeOperatorKey = activeOperatorKey(operatorKey);
ReceivingContainerSession active = sessions.lockActiveByOperator(activeOperatorKey);
if (active != null) {
if (!containerNo.equals(active.getContainerNo())) throw conflict("Finish the current container before selecting another one");
ReceivingContainerResponses.Session result = snapshot(active, containerPlans);
save(active, "PREPARE", request.idempotencyKey(), command, result); return result;
}
if (sessions.lockActiveByContainer(customerId, activeContainerKey(customerId, containerNo)) != null) {
if (sessions.lockActiveByContainer(activeContainerKey(containerNo)) != null) {
throw conflict("Container is already signed or prepared by another account");
}
LocalDateTime now = LocalDateTime.now();
ReceivingContainerSession session = new ReceivingContainerSession();
session.setId(IdWorker.getId()); session.setCustomerId(customerId); session.setContainerNo(containerNo);
session.setOperatorKey(operatorKey); session.setOperatorName(operatorName);
session.setActiveOperatorKey(activeOperatorKey); session.setActiveContainerKey(activeContainerKey(customerId, containerNo));
session.setActiveOperatorKey(activeOperatorKey); session.setActiveContainerKey(activeContainerKey(containerNo));
session.setStatus("WAIT_SIGN"); session.setPreparedAt(now); session.setCreatedAt(now); session.setUpdatedAt(now);
session.setVersion(0);
try { sessions.insert(session); }
@@ -138,20 +141,21 @@ public class ReceivingContainerService {
save(session, "PREPARE", request.idempotencyKey(), command, result); return result;
}
public ReceivingContainerResponses.Photo uploadPhoto(String customerId, String operatorKey, long operatorId,
MultipartFile file) {
ReceivingContainerSession session = requireCurrent(customerId, operatorKey, true);
public ReceivingContainerResponses.Photo uploadPhoto(String operatorKey, long operatorId, MultipartFile file) {
ReceivingContainerSession session = requireCurrent(operatorKey, true);
String customerId = session.getCustomerId();
if (!"WAIT_SIGN".equals(session.getStatus())) throw conflict("Unload photos can only be uploaded before signing");
UnloadPhotoFileGateway.UploadedFile uploaded = unloadPhotos.upload(customerId, session.getId(), operatorId, file);
return new ReceivingContainerResponses.Photo(uploaded.fileId(), uploaded.fileName(), uploaded.contentType(), uploaded.sizeBytes());
}
@Transactional
public ReceivingContainerResponses.Session sign(String customerId, String operatorKey, String operatorName,
public ReceivingContainerResponses.Session sign(String operatorKey, String operatorName,
ReceivingContainerRequests.Sign request) {
ReceivingContainerOperation replay = replay(customerId, operatorKey, "SIGN", request.idempotencyKey(), request);
ReceivingContainerOperation replay = replay(operatorKey, "SIGN", request.idempotencyKey(), request);
if (replay != null) return read(replay, ReceivingContainerResponses.Session.class);
ReceivingContainerSession session = requireCurrent(customerId, operatorKey, true);
ReceivingContainerSession session = requireCurrent(operatorKey, true);
String customerId = session.getCustomerId();
if (!"WAIT_SIGN".equals(session.getStatus())) throw conflict("Container has already been signed");
List<ReceivingContainerResponses.SignedPhoto> photos = validateSignPhotos(request);
List<Long> photoFileIds = photos.stream().map(ReceivingContainerResponses.SignedPhoto::fileId).toList();
@@ -166,14 +170,15 @@ public class ReceivingContainerService {
save(session, "SIGN", request.idempotencyKey(), request, result); return result;
}
public ReceivingContainerResponses.Session current(String customerId, String operatorKey) {
ReceivingContainerSession session = requireCurrent(customerId, operatorKey, false);
return snapshot(session, requirePlans(customerId, session.getContainerNo()));
public ReceivingContainerResponses.Session current(String operatorKey) {
ReceivingContainerSession session = requireCurrent(operatorKey, false);
return snapshot(session, requirePlans(session.getCustomerId(), session.getContainerNo()));
}
public ReceivingContainerResponses.CartonResolved resolveCarton(String customerId, String operatorKey,
public ReceivingContainerResponses.CartonResolved resolveCarton(String operatorKey,
ReceivingContainerRequests.CartonResolve request) {
ReceivingContainerSession session = requireSigned(customerId, operatorKey);
ReceivingContainerSession session = requireSigned(operatorKey);
String customerId = session.getCustomerId();
InboundPlanCarton carton = cartons.selectOne(new LambdaQueryWrapper<InboundPlanCarton>()
.eq(InboundPlanCarton::getCustomerId, customerId).eq(InboundPlanCarton::getCartonMark, request.cartonMark()));
if (carton == null) throw notFound("Carton mark was not found");
@@ -190,9 +195,10 @@ public class ReceivingContainerService {
inbound.plannedPalletCount(), inbound.totalPackages(), inbound.receivedPackages(), inbound.pendingPackages());
}
public ReceivingContainerResponses.SkuResolved resolveSku(String customerId, String operatorKey,
public ReceivingContainerResponses.SkuResolved resolveSku(String operatorKey,
ReceivingContainerRequests.SkuResolve request) {
ReceivingContainerSession session = requireSigned(customerId, operatorKey);
ReceivingContainerSession session = requireSigned(operatorKey);
String customerId = session.getCustomerId();
List<SkuRef> matches = refs(customerId, session.getContainerNo()).stream()
.filter(ref -> "FBA".equals(destinationType(ref.line(), ref.order())))
.map(ref -> skuRef(ref, request.skuCode()))
@@ -203,9 +209,10 @@ public class ReceivingContainerService {
return skuResponse(matches.get(0));
}
public ReceivingContainerResponses.PalletResolved resolvePallet(String customerId, String operatorKey,
public ReceivingContainerResponses.PalletResolved resolvePallet(String operatorKey,
ReceivingContainerRequests.PalletResolve request) {
ReceivingContainerSession session = requireSigned(customerId, operatorKey);
ReceivingContainerSession session = requireSigned(operatorKey);
String customerId = session.getCustomerId();
InboundRef ref = requireInbound(customerId, session.getContainerNo(), request.inboundCode());
WarehouseResourceGateway.WarehousePalletReference pallet = warehouse.resolvePallet(request.palletCode());
validatePallet(ref, pallet);
@@ -214,11 +221,12 @@ public class ReceivingContainerService {
}
@Transactional
public ReceivingContainerResponses.Receipt receiveBulk(String customerId, String operatorKey, String operatorName,
public ReceivingContainerResponses.Receipt receiveBulk(String operatorKey, String operatorName,
ReceivingContainerRequests.BulkReceive request) {
ReceivingContainerOperation replay = replay(customerId, operatorKey, "BULK_RECEIVE", request.idempotencyKey(), request);
ReceivingContainerOperation replay = replay(operatorKey, "BULK_RECEIVE", request.idempotencyKey(), request);
if (replay != null) return read(replay, ReceivingContainerResponses.Receipt.class);
ReceivingContainerSession session = requireSigned(customerId, operatorKey);
ReceivingContainerSession session = requireSigned(operatorKey);
String customerId = session.getCustomerId();
InboundRef ref = requireInbound(customerId, session.getContainerNo(), request.inboundCode());
if (!"FBA".equals(destinationType(ref.line(), ref.order()))) {
throw conflict("OVERSEAS destination must use carton-by-carton scanning; bulk count is forbidden");
@@ -248,11 +256,12 @@ public class ReceivingContainerService {
}
@Transactional
public ReceivingContainerResponses.Receipt receiveCarton(String customerId, String operatorKey, String operatorName,
public ReceivingContainerResponses.Receipt receiveCarton(String operatorKey, String operatorName,
ReceivingContainerRequests.CartonReceive request) {
ReceivingContainerOperation replay = replay(customerId, operatorKey, "CARTON_RECEIVE", request.idempotencyKey(), request);
ReceivingContainerOperation replay = replay(operatorKey, "CARTON_RECEIVE", request.idempotencyKey(), request);
if (replay != null) return read(replay, ReceivingContainerResponses.Receipt.class);
ReceivingContainerSession session = requireSigned(customerId, operatorKey);
ReceivingContainerSession session = requireSigned(operatorKey);
String customerId = session.getCustomerId();
InboundPlanCarton carton = cartons.selectOne(new LambdaQueryWrapper<InboundPlanCarton>()
.eq(InboundPlanCarton::getCustomerId, customerId).eq(InboundPlanCarton::getCartonMark, request.cartonMark()));
if (carton == null) throw notFound("Carton mark was not found");
@@ -284,11 +293,12 @@ public class ReceivingContainerService {
}
@Transactional
public ReceivingContainerResponses.Session complete(String customerId, String operatorKey,
public ReceivingContainerResponses.Session complete(String operatorKey,
ReceivingContainerRequests.Action request) {
ReceivingContainerOperation replay = replay(customerId, operatorKey, "COMPLETE", request.idempotencyKey(), request);
ReceivingContainerOperation replay = replay(operatorKey, "COMPLETE", request.idempotencyKey(), request);
if (replay != null) return read(replay, ReceivingContainerResponses.Session.class);
ReceivingContainerSession session = requireSigned(customerId, operatorKey);
ReceivingContainerSession session = requireSigned(operatorKey);
String customerId = session.getCustomerId();
List<InboundPlan> containerPlans = requirePlans(customerId, session.getContainerNo());
Totals totals = totals(containerPlans);
if (totals.total() != totals.received()) throw conflict("Every inbound code must be fully received before completing the container");
@@ -301,22 +311,33 @@ public class ReceivingContainerService {
save(session, "COMPLETE", request.idempotencyKey(), request, result); return result;
}
private ReceivingContainerSession requireCurrent(String customerId, String operatorKey, boolean lock) {
String key = activeOperatorKey(customerId, operatorKey);
ReceivingContainerSession session = lock ? sessions.lockActiveByOperator(customerId, key)
: sessions.findActiveByOperator(customerId, key);
private ReceivingContainerSession requireCurrent(String operatorKey, boolean lock) {
String key = activeOperatorKey(operatorKey);
ReceivingContainerSession session = lock ? sessions.lockActiveByOperator(key)
: sessions.findActiveByOperator(key);
if (session == null) throw notFound("Current receiving container session was not found");
return session;
}
private ReceivingContainerSession requireSigned(String customerId, String operatorKey) {
ReceivingContainerSession session = requireCurrent(customerId, operatorKey, true);
private ReceivingContainerSession requireSigned(String operatorKey) {
ReceivingContainerSession session = requireCurrent(operatorKey, true);
if (!List.of("SIGNED", "RECEIVING").contains(session.getStatus())) {
throw conflict("Sign the receiving container before receiving cargo");
}
return session;
}
private List<InboundPlan> requirePlans(String containerNo) {
List<InboundPlan> values = plans.selectList(new LambdaQueryWrapper<InboundPlan>()
.eq(InboundPlan::getContainerNo, containerNo).orderByAsc(InboundPlan::getId));
if (values.isEmpty()) throw notFound("Receiving container was not found");
requireSingleCustomer(values, containerNo);
if (values.stream().allMatch(value -> "COMPLETED".equals(value.getStatus()))) {
throw conflict("Receiving container is already completed");
}
return values;
}
private List<InboundPlan> requirePlans(String customerId, String containerNo) {
List<InboundPlan> values = plansForContainer(customerId, containerNo);
if (values.isEmpty()) throw notFound("Receiving container was not found");
@@ -334,6 +355,15 @@ public class ReceivingContainerService {
return values;
}
private String requireSingleCustomer(List<InboundPlan> values, String containerNo) {
Set<String> customers = values.stream().map(InboundPlan::getCustomerId).map(ReceivingContainerService::trim)
.filter(Objects::nonNull).collect(Collectors.toCollection(java.util.LinkedHashSet::new));
if (customers.size() != 1 || values.stream().anyMatch(value -> trim(value.getCustomerId()) == null)) {
throw conflict("Receiving container must resolve to exactly one customer: " + containerNo);
}
return customers.iterator().next();
}
private List<InboundRef> refs(String customerId, String containerNo) {
List<InboundRef> result = new ArrayList<>();
for (InboundPlan plan : plansForContainer(customerId, containerNo)) {
@@ -576,9 +606,9 @@ public class ReceivingContainerService {
return value;
}
private ReceivingContainerOperation replay(String customerId, String operatorKey, String action, String key, Object request) {
private ReceivingContainerOperation replay(String operatorKey, String action, String key, Object request) {
key = idempotencyKey(key);
ReceivingContainerOperation value = operations.find(customerId, operatorKey, key);
ReceivingContainerOperation value = operations.find(operatorKey, key);
if (value == null) return null;
if (!action.equals(value.getAction()) || !hash(request).equals(value.getRequestHash())) {
throw conflict("Idempotency key was already used with a different request");
@@ -618,8 +648,8 @@ public class ReceivingContainerService {
catch (JsonProcessingException exception) { throw new IllegalStateException(exception); }
}
private static String activeOperatorKey(String customerId, String operatorKey) { return customerId + ":" + operatorKey; }
private static String activeContainerKey(String customerId, String containerNo) { return customerId + ":" + containerNo; }
private static String activeOperatorKey(String operatorKey) { return "OPERATOR:" + operatorKey; }
private static String activeContainerKey(String containerNo) { return "CONTAINER:" + containerNo.trim().toUpperCase(java.util.Locale.ROOT); }
private static String idempotencyKey(String value) {
if (value == null || value.isBlank()) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Idempotency key is required");
value = value.trim();
@@ -1,6 +1,7 @@
package com.baiyue.lastleg.inventory.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -40,8 +41,11 @@ public class InventoryLedger {
private Long deltaPackages;
private Long afterPackages;
/** Quantity snapshot is retained even though current PDA operations are carton-count based. */
@TableField(exist = false)
private Long beforeQuantity;
@TableField(exist = false)
private Long deltaQuantity;
@TableField(exist = false)
private Long afterQuantity;
private String operatorId;
private String remark;
@@ -1,6 +1,8 @@
package com.baiyue.lastleg.procurement.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -17,24 +19,24 @@ public class OverseasProcurementOrder {
private String orderNo;
private String purchaseCategory;
private String productName;
private String productSpecification;
private String productUrl;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String productSpecification;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String productUrl;
private Long purchaseQuantity;
private BigDecimal budgetUnitPrice;
private String currency;
private String receivingAddress;
private String contactName;
private String contactPhone;
private String purchaseWarehouseId;
private String purchaseWarehouseName;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String purchaseWarehouseId;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String purchaseWarehouseName;
private Long inboundQuantity;
private String domesticCarrier;
private String domesticTrackingNo;
private String overseasCarrier;
private String overseasTrackingNo;
private LocalDateTime estimatedArrivalAt;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String domesticCarrier;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String domesticTrackingNo;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String overseasCarrier;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String overseasTrackingNo;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private LocalDateTime estimatedArrivalAt;
private String status;
private String remark;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String remark;
private String idempotencyKey;
private String requestHash;
private String createdBy;