提交
This commit is contained in:
+9
@@ -74,6 +74,15 @@ public class MerchantRelabelApplicationController {
|
||||
if ("ITEM".equalsIgnoreCase(scope)) return ApiResponse.success(service.uploadItemFile(customer(user), relabelNo, itemId == null ? 0L : itemId, operatorLong(user), file));
|
||||
return ApiResponse.success(service.uploadTotalFile(customer(user), relabelNo, operatorLong(user), file, kind));
|
||||
}
|
||||
/** 按登录客户及申请绑定关系读取贴标资料,禁止跨客户或跨明细下载。 */
|
||||
@GetMapping("/{relabelNo}/files/{fileId}/download")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_LIST + "')")
|
||||
public ResponseEntity<byte[]> downloadFile(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String relabelNo, @PathVariable long fileId, @RequestParam String scope,
|
||||
@RequestParam(required = false) Long itemId) {
|
||||
var file = service.downloadFile(customer(user), relabelNo, scope, itemId, fileId);
|
||||
return download(file.content(), file.fileName(), file.contentType());
|
||||
}
|
||||
@GetMapping("/{relabelNo}/shipping-label/{fileId}/download")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_RELABEL_LIST + "')")
|
||||
public ResponseEntity<byte[]> downloadShippingLabel(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
|
||||
+11
@@ -134,6 +134,17 @@ public class RelabelApplicationController {
|
||||
return ResponseEntity.ok().cacheControl(org.springframework.http.CacheControl.noStore()).header("Content-Disposition", ContentDisposition.inline().filename(file.fileName(), StandardCharsets.UTF_8).build().toString())
|
||||
.contentType(org.springframework.http.MediaType.parseMediaType(file.contentType())).contentLength(file.content().length).body(file.content());
|
||||
}
|
||||
/** 平台按申请绑定关系读取客户上传的面单。 */
|
||||
@GetMapping("/{relabelNo}/shipping-label/{fileId}/download")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.RELABEL_ORDER_LIST + "')")
|
||||
public ResponseEntity<byte[]> downloadShippingLabel(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String relabelNo, @PathVariable long fileId) {
|
||||
requirePlatform(user);
|
||||
var file = service.downloadShippingLabel(service.customerId(relabelNo), relabelNo, fileId);
|
||||
return ResponseEntity.ok().cacheControl(org.springframework.http.CacheControl.noStore())
|
||||
.header("Content-Disposition", ContentDisposition.inline().filename(file.fileName(), StandardCharsets.UTF_8).build().toString())
|
||||
.contentType(org.springframework.http.MediaType.parseMediaType(file.contentType())).contentLength(file.content().length).body(file.content());
|
||||
}
|
||||
@GetMapping("/{relabelNo}/completion-photos/{fileId}/download")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.RELABEL_ORDER_LIST + "')")
|
||||
public ResponseEntity<byte[]> downloadCompletionPhoto(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
|
||||
+12
@@ -20,6 +20,7 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
public class WarehouseTransferController {
|
||||
public static final String MANAGE_PERMISSION = "lastleg:warehouse-transfer:manage";
|
||||
@Resource private WarehouseTransferService service;
|
||||
@Resource private com.baiyue.lastleg.transfer.service.WarehouseTransferForecastService forecasts;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> create(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@@ -41,6 +42,17 @@ public class WarehouseTransferController {
|
||||
return ApiResponse.success(service.detail(scope(user, customerId), transferNo));
|
||||
}
|
||||
|
||||
/** 平台人员在测量完成前追加整单预报箱,沿用统一锁单及箱唛生成规则。 */
|
||||
@PostMapping("/{transferNo}/increase-forecast-cartons")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.WAREHOUSE_TRANSFER_APPROVE + "')")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> increaseForecastCartons(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam String customerId, @PathVariable String transferNo,
|
||||
@Valid @RequestBody WarehouseTransferRequests.IncreaseForecastCartons request) {
|
||||
if (user == null || !"PLATFORM".equals(user.appCode()) || user.tenantId() == null || user.tenantId() != 0L)
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Platform identity is required");
|
||||
return ApiResponse.success(forecasts.increase(scope(user, customerId), transferNo, operator(user), request));
|
||||
}
|
||||
|
||||
@GetMapping("/{transferNo}/carton-label")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.LAST_MILE_ORDER_LIST + "')")
|
||||
public ApiResponse<WarehouseTransferResponses.Label> cartonLabel(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
|
||||
+1
-3
@@ -105,11 +105,9 @@ public final class WarehouseTransferRequests {
|
||||
public record Outbound(@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
/** 仅增加既有SKU的预报箱数,携带原箱数用于并发核对及重试去重。 */
|
||||
/** 仅增加整单预报箱数,箱内SKU由PDA清点确定,携带原箱数用于并发核对及重试去重。 */
|
||||
public record IncreaseForecastCartons(
|
||||
@Positive @Max(5000) int expectedPackageCount,
|
||||
@NotNull Long transferItemId,
|
||||
@Min(0) @Max(5000) int expectedItemPackageCount,
|
||||
@Positive @Max(5000) int packageCount
|
||||
) {}
|
||||
|
||||
|
||||
+5
-18
@@ -6,7 +6,6 @@ import com.baiyue.lastleg.transfer.entity.*;
|
||||
import com.baiyue.lastleg.transfer.mapper.*;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegInboundOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderDetail;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.mapper.*;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanLine;
|
||||
@@ -20,11 +19,11 @@ import java.util.List;
|
||||
@Repository
|
||||
public class TransferForecastRepository {
|
||||
@Resource private WarehouseTransferOrderMapper transfers;
|
||||
@Resource private WarehouseTransferItemMapper items;
|
||||
|
||||
@Resource private WarehouseTransferCartonMapper cartons;
|
||||
@Resource private LastlegOrderMapper orders;
|
||||
@Resource private LastlegInboundOrderMapper inboundOrders;
|
||||
@Resource private LastlegOrderDetailMapper orderDetails;
|
||||
|
||||
@Resource private LastlegOrderStatusHistoryMapper histories;
|
||||
@Resource private InboundPlanLineMapper planLines;
|
||||
|
||||
@@ -33,26 +32,19 @@ public class TransferForecastRepository {
|
||||
return transfers.lockNo(customerId, transferNo);
|
||||
}
|
||||
|
||||
/** 按创建顺序读取预报明细,保持关联尾程明细的序号稳定。 */
|
||||
public List<WarehouseTransferItem> items(String customerId, Long transferId) {
|
||||
return items.selectList(new LambdaQueryWrapper<WarehouseTransferItem>()
|
||||
.eq(WarehouseTransferItem::getCustomerId, customerId)
|
||||
.eq(WarehouseTransferItem::getTransferOrderId, transferId).orderByAsc(WarehouseTransferItem::getId));
|
||||
}
|
||||
|
||||
/** 读取原箱号用于确定新增编号,不删除或重建原箱。 */
|
||||
public List<WarehouseTransferCarton> cartons(String customerId, Long transferId) {
|
||||
return cartons.listOrder(customerId, transferId);
|
||||
}
|
||||
|
||||
/** 追加预报箱并同步各预报口径;清点数据保留,整单清点确认失效。 */
|
||||
public void append(WarehouseTransferOrder transfer, WarehouseTransferItem item, int itemIndex,
|
||||
public void append(WarehouseTransferOrder transfer,
|
||||
List<WarehouseTransferCarton> added, LocalDateTime now, String operatorId) {
|
||||
String customerId = transfer.getCustomerId();
|
||||
var order = orders.selectOne(new LambdaQueryWrapper<LastlegOrder>()
|
||||
.eq(LastlegOrder::getCustomerId, customerId).eq(LastlegOrder::getId, transfer.getLastlegOrderId()));
|
||||
if (order == null) throw new IllegalStateException("转运关联尾程订单不存在");
|
||||
items.updateById(item);
|
||||
|
||||
transfers.updateById(transfer);
|
||||
for (var carton : added) cartons.insert(carton);
|
||||
cartons.update(null, new LambdaUpdateWrapper<WarehouseTransferCarton>()
|
||||
@@ -65,18 +57,13 @@ public class TransferForecastRepository {
|
||||
inboundOrders.update(null, new LambdaUpdateWrapper<LastlegInboundOrder>()
|
||||
.eq(LastlegInboundOrder::getCustomerId, customerId).eq(LastlegInboundOrder::getId, transfer.getInboundOrderId())
|
||||
.set(LastlegInboundOrder::getPackageCount, transfer.getPackageCount()).set(LastlegInboundOrder::getUpdatedAt, now));
|
||||
int updated = orderDetails.update(null, new LambdaUpdateWrapper<LastlegOrderDetail>()
|
||||
.eq(LastlegOrderDetail::getCustomerId, customerId).eq(LastlegOrderDetail::getOrderId, transfer.getLastlegOrderId())
|
||||
.eq(LastlegOrderDetail::getSourceInboundDetailId, transfer.getTransferNo() + ":" + itemIndex)
|
||||
.set(LastlegOrderDetail::getPackageCount, item.getPackageCount()));
|
||||
if (updated != 1) throw new IllegalStateException("转运关联尾程货物明细不完整");
|
||||
planLines.update(null, new LambdaUpdateWrapper<InboundPlanLine>()
|
||||
.eq(InboundPlanLine::getCustomerId, customerId).eq(InboundPlanLine::getOrderId, transfer.getLastlegOrderId())
|
||||
.set(InboundPlanLine::getRequiredPackageCount, transfer.getPackageCount()));
|
||||
var history = new LastlegOrderStatusHistory(); history.setCustomerId(customerId); history.setOrderId(transfer.getLastlegOrderId());
|
||||
history.setFromStatus(order.getCurrentStatus()); history.setToStatus(order.getCurrentStatus());
|
||||
history.setEventId(transfer.getTransferNo()); history.setCreatedAt(now);
|
||||
history.setRemark("客户增加预报箱数,补充箱唛" + added.size() + "个;操作人:" + operatorId);
|
||||
history.setRemark("增加预报箱数,补充箱唛" + added.size() + "个;操作人:" + operatorId);
|
||||
histories.insert(history);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-12
@@ -29,17 +29,13 @@ public class WarehouseTransferForecastService {
|
||||
if (transfer == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "转运单不存在");
|
||||
if (!transferNo.equals(transfer.getInboundCode()) || !Set.of("PENDING_REVIEW", "WAIT_PDA_RECEIVING").contains(transfer.getStatus()))
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "仅测量完成前的外来转运单可增加预报箱数");
|
||||
var items = repository.items(customerId, transfer.getId());
|
||||
var item = items.stream().filter(row -> row.getId().equals(request.transferItemId())).findFirst()
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "SKU不属于当前转运单"));
|
||||
int addedCount = request.packageCount() - request.expectedItemPackageCount();
|
||||
long total = (long) request.expectedPackageCount() + addedCount;
|
||||
int addedCount = request.packageCount() - request.expectedPackageCount();
|
||||
int total = request.packageCount();
|
||||
if (addedCount <= 0 || total > 5000 || request.expectedPackageCount() <= 0)
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "只能增加预报箱数,整单最多5000箱");
|
||||
// 相同目标值视为已完成的重试;不同原快照则要求刷新,避免多端覆盖。
|
||||
if (item.getPackageCount() == request.packageCount() && transfer.getPackageCount() == total)
|
||||
return transfers.detail(customerId, transferNo);
|
||||
if (transfer.getPackageCount() != request.expectedPackageCount() || item.getPackageCount() != request.expectedItemPackageCount())
|
||||
// 相同目标值重试不再生成箱唛;不同原快照必须刷新,防止并发覆盖。
|
||||
if (transfer.getPackageCount() == total) return transfers.detail(customerId, transferNo);
|
||||
if (transfer.getPackageCount() != request.expectedPackageCount())
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "预报箱数已变化,请刷新后重试");
|
||||
var existing = repository.cartons(customerId, transfer.getId());
|
||||
int index = existing.stream().mapToInt(WarehouseTransferCarton::getCartonIndex).max().orElse(0);
|
||||
@@ -47,13 +43,14 @@ public class WarehouseTransferForecastService {
|
||||
var added = new ArrayList<WarehouseTransferCarton>();
|
||||
for (int i = 0; i < addedCount; i++) {
|
||||
var carton = new WarehouseTransferCarton(); carton.setId(IdWorker.getId()); carton.setCustomerId(customerId);
|
||||
carton.setTransferOrderId(transfer.getId()); carton.setTransferItemId(item.getId());
|
||||
carton.setTransferOrderId(transfer.getId());
|
||||
// 预报箱仅代表一个箱唛,不预先绑定SKU,实收箱内明细由PDA记录。
|
||||
carton.setCartonMark(LastlegOrderNoGenerator.compactCartonMark(carton.getId()));
|
||||
carton.setCartonIndex(++index); carton.setTotalCartons((int) total); carton.setStatus("EXPECTED");
|
||||
carton.setCreatedAt(now); carton.setUpdatedAt(now); added.add(carton);
|
||||
}
|
||||
item.setPackageCount(request.packageCount()); transfer.setPackageCount((int) total); transfer.setUpdatedAt(now);
|
||||
repository.append(transfer, item, items.indexOf(item), added, now, operatorId);
|
||||
transfer.setPackageCount(total); transfer.setUpdatedAt(now);
|
||||
repository.append(transfer, added, now, operatorId);
|
||||
return transfers.detail(customerId, transferNo);
|
||||
}
|
||||
}
|
||||
|
||||
+18
-8
@@ -410,9 +410,13 @@ public class WarehouseTransferService {
|
||||
inboundOrders.updateById(inbound);
|
||||
}
|
||||
|
||||
// 草稿可能已打印箱唛,编辑时保留已有序号对应的编号,只追加或移除尾部箱子。
|
||||
Map<Integer, WarehouseTransferCarton> originalCartons = transferCartons.listOrder(customerId, transfer.getId())
|
||||
.stream().collect(Collectors.toMap(WarehouseTransferCarton::getCartonIndex, Function.identity()));
|
||||
transferCartons.delete(new LambdaQueryWrapper<WarehouseTransferCarton>()
|
||||
.eq(WarehouseTransferCarton::getCustomerId, customerId)
|
||||
.eq(WarehouseTransferCarton::getTransferOrderId, transfer.getId()));
|
||||
.eq(WarehouseTransferCarton::getTransferOrderId, transfer.getId())
|
||||
.gt(WarehouseTransferCarton::getCartonIndex, request.packageCount()));
|
||||
transferItems.delete(new LambdaQueryWrapper<WarehouseTransferItem>()
|
||||
.eq(WarehouseTransferItem::getCustomerId, customerId)
|
||||
.eq(WarehouseTransferItem::getTransferOrderId, transfer.getId()));
|
||||
@@ -431,8 +435,16 @@ public class WarehouseTransferService {
|
||||
List<String> customerMarks = splitMarks(source.shippingMark());
|
||||
for (int itemCarton = 0; itemCarton < source.packageCount(); itemCarton++) {
|
||||
cartonIndex++;
|
||||
insertTransferCarton(customerId, transfer.getId(), itemId, transferNo, cartonIndex,
|
||||
request.packageCount(), itemCarton < customerMarks.size() ? customerMarks.get(itemCarton) : null, now);
|
||||
var existingCarton = originalCartons.get(cartonIndex);
|
||||
String customerMark = itemCarton < customerMarks.size() ? customerMarks.get(itemCarton) : null;
|
||||
if (existingCarton == null) {
|
||||
insertTransferCarton(customerId, transfer.getId(), itemId, transferNo, cartonIndex,
|
||||
request.packageCount(), customerMark, now);
|
||||
} else {
|
||||
existingCarton.setTransferItemId(itemId); existingCarton.setTotalCartons(request.packageCount());
|
||||
existingCarton.setCustomerCartonMark(customerMark); existingCarton.setUpdatedAt(now);
|
||||
transferCartons.updateById(existingCarton);
|
||||
}
|
||||
}
|
||||
insertOrderDetail(customerId, order.getId(), transferNo, index, source, request, now);
|
||||
}
|
||||
@@ -698,12 +710,11 @@ public class WarehouseTransferService {
|
||||
return cartonLabel(transfer.getCustomerId(), transferNo);
|
||||
}
|
||||
|
||||
/** 提交预报后打印整单固定箱唛,测量及未收状态不改变箱号集合。 */
|
||||
/** 保存草稿后即可打印整单固定箱唛,测量及未收状态不改变箱号集合。 */
|
||||
@Transactional(readOnly = true)
|
||||
public WarehouseTransferResponses.Label cartonLabel(String customerId, String transferNo) {
|
||||
WarehouseTransferOrder transfer = transfers.findNo(customerId, transferNo);
|
||||
if (transfer == null) throw notFound("Warehouse transfer order not found");
|
||||
if (DRAFT.equals(transfer.getStatus())) throw conflict("Submit the warehouse transfer order before printing carton labels");
|
||||
List<WarehouseTransferResponses.Carton> cartons = transferCartons.listOrder(customerId, transfer.getId()).stream()
|
||||
.map(value -> new WarehouseTransferResponses.Carton(value.getCartonMark(), value.getCustomerCartonMark(), value.getCartonIndex(), value.getTotalCartons(), value.getStatus(), value.getTransferItemId(), value.getTransferPalletId(), value.getLengthCm(), value.getWidthCm(), value.getHeightCm(), value.getGrossWeightKg(), value.getCountedQuantity(), value.getDiscrepancyReason(), value.getCountedBy(), value.getCountedAt(), cartonContents(value))).toList();
|
||||
StringBuilder labels = new StringBuilder();
|
||||
@@ -930,8 +941,6 @@ public class WarehouseTransferService {
|
||||
var carton = byMark.get(input.cartonMark());
|
||||
if (carton == null || !seen.add(input.cartonMark()))
|
||||
throw badRequest("箱号不属于本单或重复填写");
|
||||
var item = byItem.get(carton.getTransferItemId());
|
||||
if (item == null) throw badRequest("箱子申报明细不属于本单");
|
||||
if (input.items() == null || input.items().isEmpty() || input.items().size() > 200)
|
||||
throw badRequest("每箱必须填写 1 至 200 行 SKU");
|
||||
Set<String> skuCodes = new java.util.HashSet<>();
|
||||
@@ -941,7 +950,7 @@ public class WarehouseTransferService {
|
||||
|| !skuCodes.add(line.skuCode())) throw badRequest("箱内 SKU 必须来自本单预报,同箱不能重复");
|
||||
if (line.quantity() <= 0 || line.quantity() > Integer.MAX_VALUE) throw badRequest("每行 SKU 必须填写有效实点件数");
|
||||
}
|
||||
if (!skuCodes.contains(item.getSkuCode())) throw badRequest("请保留箱子原申报 SKU,可添加其他本单 SKU");
|
||||
// 箱内实际SKU以PDA清点为准,允许混箱或调整,仍须属于当前单预报。
|
||||
if (input.length() == null || input.width() == null
|
||||
|| input.height() == null || input.weight() == null || input.length().signum() <= 0 || input.width().signum() <= 0
|
||||
|| input.height().signum() <= 0 || input.weight().signum() <= 0) throw badRequest("每箱必须填写有效尺寸、重量和实点件数");
|
||||
@@ -963,6 +972,7 @@ public class WarehouseTransferService {
|
||||
var declared = byItem.values().stream().filter(value -> value.getSkuCode().equals(line.skuCode())).findFirst().orElseThrow();
|
||||
return new WarehouseTransferResponses.CartonItem(declared.getId(), line.skuCode(), line.quantity());
|
||||
}).toList();
|
||||
carton.setTransferItemId(contents.get(0).transferItemId());
|
||||
try { carton.setSkuItemsJson(objectMapper.writeValueAsString(contents)); }
|
||||
catch (com.fasterxml.jackson.core.JsonProcessingException e) { throw new IllegalStateException("Cannot serialize carton contents", e); }
|
||||
carton.setCountedBy(operatorId); carton.setCountedAt(now); carton.setStatus("COUNTED");
|
||||
|
||||
Reference in New Issue
Block a user