feat: support mixed-SKU dropship cartons
This commit is contained in:
@@ -65,18 +65,18 @@ public interface OrderCargoClient {
|
||||
record CargoLabelBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
String skuNo, Integer packageCount, Integer unitsPerCarton,
|
||||
BigDecimal lengthCm, BigDecimal widthCm, BigDecimal heightCm,
|
||||
String shipmentId) {
|
||||
String shipmentId, String packingGroupNo) {
|
||||
public CargoLabelBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
Integer packageCount, BigDecimal lengthCm, BigDecimal widthCm,
|
||||
BigDecimal heightCm) {
|
||||
this(cargoId, orderId, itemNo, cargoName, null,
|
||||
packageCount, null, lengthCm, widthCm, heightCm, null);
|
||||
packageCount, null, lengthCm, widthCm, heightCm, null, null);
|
||||
}
|
||||
public CargoLabelBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
String skuNo, Integer packageCount, BigDecimal lengthCm,
|
||||
BigDecimal widthCm, BigDecimal heightCm, String shipmentId) {
|
||||
this(cargoId, orderId, itemNo, cargoName, skuNo,
|
||||
packageCount, null, lengthCm, widthCm, heightCm, shipmentId);
|
||||
packageCount, null, lengthCm, widthCm, heightCm, shipmentId, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,12 @@ public interface PrintPdfClient {
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String requestId,
|
||||
@RequestBody FreightA4PdfRequest request);
|
||||
|
||||
@PostMapping(value = "/dropship-pallet-a4-documents", consumes = "application/json")
|
||||
ResponseEntity<byte[]> generateDropshipPalletA4Documents(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String requestId,
|
||||
@RequestBody DropshipPalletA4PdfRequest request);
|
||||
|
||||
record InboundMarkPdfRequest(String templateCode, PageSpec pageSpec,
|
||||
String downloadFileName, List<Label> labels) {
|
||||
}
|
||||
@@ -44,4 +50,14 @@ public interface PrintPdfClient {
|
||||
|
||||
record CargoRow(String marksAndNos, String quantity, String packaging,
|
||||
String descriptionOfGoods, String grossWeight, String measurement) {}
|
||||
|
||||
record DropshipPalletA4PdfRequest(String templateCode, PageSpec pageSpec,
|
||||
String downloadFileName, List<DropshipPalletDocument> documents) {}
|
||||
|
||||
record DropshipPalletDocument(String palletNo, String inboundCodes, int cartonCount,
|
||||
int skuCount, BigDecimal weightKg, BigDecimal volumeCbm,
|
||||
String barcodeContent, List<DropshipPalletSkuRow> skuRows) {}
|
||||
|
||||
record DropshipPalletSkuRow(String inboundCode, String skuCode, String cargoName,
|
||||
int cartonCount, int skuQuantity) {}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.PositiveOrZero;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@@ -19,12 +22,16 @@ public final class DropshipPalletizationDtos {
|
||||
|
||||
public record CountCartonRequest(@Positive long warehouseId,
|
||||
@NotBlank @Size(max = 80) String boxCode,
|
||||
@Positive int actualSkuQuantity,
|
||||
@NotNull DiscrepancyAction discrepancyAction,
|
||||
@Size(max = 500) String discrepancyRemark,
|
||||
@NotEmpty @Valid List<CountSkuLine> skuLines,
|
||||
@Pattern(regexp = "DP-[0-9]{6}-[0-9]{4}") String palletNo,
|
||||
@NotBlank @Size(max = 128) String operationId) {}
|
||||
|
||||
public record CountSkuLine(@Positive long inboundDetailId,
|
||||
@NotBlank @Size(max = 128) String skuCode,
|
||||
@PositiveOrZero int actualQuantity,
|
||||
@NotNull DiscrepancyAction discrepancyAction,
|
||||
@Size(max = 500) String discrepancyRemark) {}
|
||||
|
||||
public record BindPendingRequest(@Positive long warehouseId,
|
||||
@NotBlank @Pattern(regexp = "DP-[0-9]{6}-[0-9]{4}") String palletNo,
|
||||
@NotBlank @Size(max = 128) String operationId) {}
|
||||
@@ -63,12 +70,14 @@ public final class DropshipPalletizationDtos {
|
||||
List<String> cartonMarks) {}
|
||||
|
||||
public record Carton(Long cartonId, String boxCode, Long inboundId, String inboundCode,
|
||||
Long inboundDetailId, String skuCode, String cargoName,
|
||||
Integer expectedSkuQuantity, Integer actualSkuQuantity,
|
||||
String discrepancyStatus, String discrepancyRemark,
|
||||
List<CartonSkuLine> skuLines,
|
||||
String countStatus, Long palletId, String palletNo,
|
||||
Boolean canCount, String blockReason) {}
|
||||
|
||||
public record CartonSkuLine(Long inboundDetailId, String skuCode, String cargoName,
|
||||
Integer expectedQuantity, Integer actualQuantity,
|
||||
String discrepancyStatus, String discrepancyRemark) {}
|
||||
|
||||
public record CountResult(Carton carton, Integer countedCartons, Integer boundCartons,
|
||||
Integer unboundCartons, Integer remainingCartons, Pallet pallet) {}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,12 @@ public interface DropshipPalletizationMapper {
|
||||
FROM fl_inbound_carton_label l JOIN fl_dropship_pallet_task t ON t.inbound_id=l.inbound_id AND t.tenant_id=l.tenant_id
|
||||
WHERE t.id=#{taskId} AND l.box_code=#{boxCode} AND l.batch_status='ACTIVE'""")
|
||||
LabelRow label(@Param("taskId") long taskId,@Param("boxCode") String boxCode);
|
||||
@Select("""
|
||||
SELECT s.inbound_detail_id,s.sku_code,s.cargo_name,s.expected_quantity,s.line_no
|
||||
FROM fl_inbound_carton_label_sku s JOIN fl_inbound_carton_label l ON l.id=s.carton_label_id
|
||||
JOIN fl_dropship_pallet_task t ON t.inbound_id=l.inbound_id AND t.tenant_id=l.tenant_id
|
||||
WHERE t.id=#{taskId} AND l.id=#{labelId} ORDER BY s.line_no
|
||||
""") List<LabelSkuRow> labelSkus(@Param("taskId") long taskId,@Param("labelId") long labelId);
|
||||
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.count_operation_id=#{operationId}") CartonRow cartonByCountOperation(@Param("operationId") String operationId);
|
||||
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.task_id=#{taskId} AND c.id=#{cartonId}") CartonRow carton(@Param("taskId") long taskId,@Param("cartonId") long cartonId);
|
||||
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.task_id=#{taskId} AND c.carton_label_id=#{labelId}")
|
||||
@@ -103,6 +109,24 @@ public interface DropshipPalletizationMapper {
|
||||
@Param("discrepancyStatus") String discrepancyStatus,@Param("discrepancyRemark") String discrepancyRemark,
|
||||
@Param("operationId") String operationId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_dropship_pallet_carton_sku
|
||||
(id,carton_id,inbound_detail_id,sku_code,cargo_name,expected_quantity,actual_quantity,
|
||||
discrepancy_status,discrepancy_remark,line_no)
|
||||
VALUES(#{id},#{cartonId},#{detailId},#{skuCode},#{cargoName},#{expectedQuantity},#{actualQuantity},
|
||||
#{discrepancyStatus},#{discrepancyRemark},#{lineNo})
|
||||
""")
|
||||
int insertCartonSku(@Param("id") long id,@Param("cartonId") long cartonId,@Param("detailId") long detailId,
|
||||
@Param("skuCode") String skuCode,@Param("cargoName") String cargoName,
|
||||
@Param("expectedQuantity") int expectedQuantity,@Param("actualQuantity") int actualQuantity,
|
||||
@Param("discrepancyStatus") String discrepancyStatus,@Param("discrepancyRemark") String discrepancyRemark,
|
||||
@Param("lineNo") int lineNo);
|
||||
|
||||
@Select("""
|
||||
SELECT inbound_detail_id,sku_code,cargo_name,expected_quantity,actual_quantity,discrepancy_status,discrepancy_remark,line_no
|
||||
FROM fl_dropship_pallet_carton_sku WHERE carton_id=#{cartonId} ORDER BY line_no
|
||||
""") List<CartonSkuRow> cartonSkus(@Param("cartonId") long cartonId);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet_task SET counted_cartons=counted_cartons+1,allocated_cartons=allocated_cartons+#{boundCount},
|
||||
discrepancy_cartons=discrepancy_cartons+#{discrepancyCount},version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
@@ -170,8 +194,9 @@ public interface DropshipPalletizationMapper {
|
||||
WHERE c.pallet_id=#{palletId}""")
|
||||
PalletMetrics metrics(@Param("palletId") long palletId);
|
||||
@Select("""
|
||||
SELECT p.id pallet_id,p.pallet_no,'SEALED_PALLET' unit_type,TRUE indivisible,COUNT(c.id) carton_count,
|
||||
COUNT(DISTINCT c.sku_code) sku_count,
|
||||
SELECT p.id pallet_id,p.pallet_no,'SEALED_PALLET' unit_type,TRUE indivisible,COUNT(DISTINCT c.id) carton_count,
|
||||
(SELECT COUNT(DISTINCT sku.sku_code) FROM fl_dropship_pallet_carton pc
|
||||
JOIN fl_dropship_pallet_carton_sku sku ON sku.carton_id=pc.id WHERE pc.pallet_id=p.id) sku_count,
|
||||
COALESCE(SUM(o.actual_gross_weight_kg/NULLIF(o.total_package_count,0)),0) weight_kg,
|
||||
COALESCE(SUM(o.actual_volume_cbm/NULLIF(o.total_package_count,0)),0) volume_cbm
|
||||
FROM fl_dropship_pallet p JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
@@ -186,23 +211,26 @@ public interface DropshipPalletizationMapper {
|
||||
SELECT p.id pallet_id,p.pallet_no,p.pallet_status,c.tenant_id account_id,c.inbound_id,o.order_id,o.order_no,
|
||||
o.inbound_code,o.warehouse_id,o.channel_code,o.channel_name,o.destination_warehouse_id,
|
||||
o.destination_code destination_warehouse_code,o.destination_name destination_warehouse_name,o.customer_name,
|
||||
c.inbound_detail_id,c.sku_code,MAX(c.cargo_name) cargo_name,COUNT(c.id) carton_count,
|
||||
SUM(c.actual_sku_quantity) actual_sku_quantity,
|
||||
s.inbound_detail_id,s.sku_code,MAX(s.cargo_name) cargo_name,COUNT(DISTINCT c.id) carton_count,
|
||||
SUM(s.actual_quantity) actual_sku_quantity,
|
||||
COALESCE(o.actual_gross_weight_kg/NULLIF(o.total_package_count,0),0) unit_gross_weight_kg,
|
||||
COALESCE(o.actual_net_weight_kg/NULLIF(o.total_package_count,0),0) unit_net_weight_kg,
|
||||
COALESCE(o.actual_volume_cbm/NULLIF(o.total_package_count,0),0) unit_volume_cbm
|
||||
FROM fl_dropship_pallet p JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
JOIN fl_dropship_pallet_carton_sku s ON s.carton_id=c.id
|
||||
JOIN fl_inbound_order o ON o.id=c.inbound_id AND o.tenant_id=c.tenant_id WHERE p.id=#{palletId}
|
||||
GROUP BY p.id,p.pallet_no,p.pallet_status,c.tenant_id,c.inbound_id,o.order_id,o.order_no,o.inbound_code,
|
||||
o.warehouse_id,o.channel_code,o.channel_name,o.destination_warehouse_id,o.destination_code,o.destination_name,
|
||||
o.customer_name,c.inbound_detail_id,c.sku_code,o.actual_gross_weight_kg,o.actual_net_weight_kg,o.actual_volume_cbm,o.total_package_count
|
||||
ORDER BY c.inbound_detail_id
|
||||
o.customer_name,s.inbound_detail_id,s.sku_code,o.actual_gross_weight_kg,o.actual_net_weight_kg,o.actual_volume_cbm,o.total_package_count
|
||||
ORDER BY s.inbound_detail_id
|
||||
""") List<PalletLine> sealedPalletLines(@Param("palletId") long palletId);
|
||||
|
||||
@Getter @Setter class TaskRow { private Long id,tenantId,inboundId,warehouseId,assigneeId; private String inboundCode,customerName,warehouseName,taskStatus,assigneeName,processStatus,fulfillmentType; private Integer totalCartons,countedCartons,allocatedCartons,discrepancyCartons,expectedPalletCount,version; }
|
||||
@Getter @Setter class PalletRow { private Long id,taskId,tenantId,inboundId,warehouseId; private String palletNo,palletStatus,sealOperationId; private Integer version; private LocalDateTime completedAt; }
|
||||
@Getter @Setter class LabelRow { private Long cartonLabelId,inboundDetailId; private String skuCode,cargoName,inboundCode,boxCode; private Integer unitsPerCarton; }
|
||||
@Getter @Setter class LabelSkuRow { private Long inboundDetailId; private String skuCode,cargoName; private Integer expectedQuantity,lineNo; }
|
||||
@Getter @Setter class CartonRow { private Long id,taskId,palletId,inboundId,inboundDetailId; private String palletNo,inboundCode,skuCode,cargoName,boxCode,discrepancyStatus,discrepancyRemark; private Integer expectedSkuQuantity,actualSkuQuantity; }
|
||||
@Getter @Setter class CartonSkuRow { private Long inboundDetailId; private String skuCode,cargoName,discrepancyStatus,discrepancyRemark; private Integer expectedQuantity,actualQuantity,lineNo; }
|
||||
record PalletMetrics(BigDecimal weightKg,BigDecimal volumeCbm) {}
|
||||
@Getter @Setter class PalletLine { private Long palletId,tenantId,accountId,inboundId,orderId,warehouseId,destinationWarehouseId,inboundDetailId; private String palletNo,palletStatus,orderNo,inboundCode,channelCode,channelName,destinationWarehouseCode,destinationWarehouseName,customerName,skuCode,cargoName; private Integer cartonCount,actualSkuQuantity; private BigDecimal unitGrossWeightKg,unitNetWeightKg,unitVolumeCbm; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** A physical carton label may freeze more than one SKU line. */
|
||||
@Mapper
|
||||
public interface InboundCartonLabelSkuMapper {
|
||||
@Insert("""
|
||||
INSERT INTO fl_inbound_carton_label_sku
|
||||
(id,tenant_id,inbound_id,carton_label_id,inbound_detail_id,sku_code,cargo_name,expected_quantity,line_no,created_at)
|
||||
VALUES(#{id},#{tenantId},#{inboundId},#{labelId},#{detailId},#{skuCode},#{cargoName},#{expectedQuantity},#{lineNo},CURRENT_TIMESTAMP(3))
|
||||
""")
|
||||
int insert(@Param("id") long id, @Param("tenantId") long tenantId,
|
||||
@Param("inboundId") long inboundId, @Param("labelId") long labelId,
|
||||
@Param("detailId") long detailId, @Param("skuCode") String skuCode,
|
||||
@Param("cargoName") String cargoName, @Param("expectedQuantity") int expectedQuantity,
|
||||
@Param("lineNo") int lineNo);
|
||||
|
||||
@Select("""
|
||||
SELECT inbound_detail_id,sku_code,cargo_name,expected_quantity,line_no
|
||||
FROM fl_inbound_carton_label_sku WHERE carton_label_id=#{labelId} ORDER BY line_no
|
||||
""")
|
||||
List<SkuRow> findByLabel(@Param("labelId") long labelId);
|
||||
|
||||
@Select("""
|
||||
SELECT carton_label_id,inbound_detail_id,sku_code,cargo_name,expected_quantity,line_no
|
||||
FROM fl_inbound_carton_label_sku WHERE tenant_id=#{tenantId} AND inbound_id=#{inboundId}
|
||||
ORDER BY carton_label_id,line_no
|
||||
""")
|
||||
List<SkuRow> findByInbound(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
|
||||
@Getter @Setter
|
||||
class SkuRow {
|
||||
private Long inboundDetailId;
|
||||
private Long cartonLabelId;
|
||||
private String skuCode;
|
||||
private String cargoName;
|
||||
private Integer expectedQuantity;
|
||||
private Integer lineNo;
|
||||
}
|
||||
}
|
||||
+49
-20
@@ -62,8 +62,10 @@ public class DropshipPalletizationService {
|
||||
else if(!Objects.equals(task.getAssigneeId(),operatorId)) block="任务不属于当前操作员";
|
||||
else if(!"WAIT_PALLETIZE".equals(task.getProcessStatus())) block="入仓单已不在待清点组托状态";
|
||||
else if(!palletSetupReady(task)) block=palletSetupMessage(task);
|
||||
return new Carton(null,label.getBoxCode(),task.getInboundId(),label.getInboundCode(),label.getInboundDetailId(),
|
||||
label.getSkuCode(),label.getCargoName(),label.getUnitsPerCarton(),null,null,null,"UNCOUNTED",null,null,
|
||||
List<CartonSkuLine> skuLines=labelSkuLines(taskId,label).stream().map(line ->
|
||||
new CartonSkuLine(line.getInboundDetailId(),line.getSkuCode(),line.getCargoName(),line.getExpectedQuantity(),
|
||||
null,null,null)).toList();
|
||||
return new Carton(null,label.getBoxCode(),task.getInboundId(),label.getInboundCode(),skuLines,"UNCOUNTED",null,null,
|
||||
block==null,block);
|
||||
}
|
||||
|
||||
@@ -74,22 +76,22 @@ public class DropshipPalletizationService {
|
||||
CartonRow replay=mapper.cartonByCountOperation(operation);
|
||||
if(replay!=null){
|
||||
if(!Objects.equals(replay.getTaskId(),taskId) || !Objects.equals(replay.getBoxCode(),request.boxCode().trim())
|
||||
|| !Objects.equals(replay.getActualSkuQuantity(),request.actualSkuQuantity())) throw conflict("清点幂等号已用于不同操作");
|
||||
|| !sameCountLines(mapper.cartonSkus(replay.getId()),request.skuLines())) throw conflict("清点幂等号已用于不同操作");
|
||||
return countResult(task,replay);
|
||||
}
|
||||
LabelRow label=requireLabel(taskId,request.boxCode());
|
||||
if(mapper.countedByLabel(taskId,label.getCartonLabelId())!=null) throw conflict("箱码已清点,不能重复清点");
|
||||
int expected=requireExpectedQuantity(label);
|
||||
boolean mismatch=expected!=request.actualSkuQuantity();
|
||||
String remark=normalize(request.discrepancyRemark());
|
||||
if(!mismatch && request.discrepancyAction()!=DiscrepancyAction.MATCH) throw invalid("实收数量一致时差异动作必须为 MATCH");
|
||||
if(mismatch && request.discrepancyAction()!=DiscrepancyAction.ACCEPT_VARIANCE) throw conflict("实收数量存在差异,请确认差异处理结果");
|
||||
if(mismatch && !StringUtils.hasText(remark)) throw invalid("接受数量差异时必须填写处理说明");
|
||||
List<CountedLine> lines=validateCountLines(labelSkuLines(taskId,label),request.skuLines());
|
||||
boolean mismatch=lines.stream().anyMatch(CountedLine::mismatch);
|
||||
PalletRow pallet=StringUtils.hasText(request.palletNo())?requireAssignablePallet(task,request.palletNo().trim()):null;
|
||||
long cartonId=IdWorker.getId();
|
||||
CountedLine primary=lines.get(0);
|
||||
mapper.insertCarton(cartonId,taskId,pallet==null?null:pallet.getId(),task.getTenantId(),task.getInboundId(),
|
||||
label.getInboundDetailId(),label.getCartonLabelId(),label.getSkuCode(),label.getCargoName(),label.getBoxCode(),
|
||||
expected,request.actualSkuQuantity(),mismatch?"ACCEPTED_VARIANCE":"MATCH",remark,operation,operatorId,operatorName);
|
||||
primary.detailId(),label.getCartonLabelId(),primary.skuCode(),primary.cargoName(),label.getBoxCode(),
|
||||
primary.expected(),primary.actual(),mismatch?"ACCEPTED_VARIANCE":"MATCH",null,operation,operatorId,operatorName);
|
||||
int lineNo=0;
|
||||
for(CountedLine line:lines) mapper.insertCartonSku(IdWorker.getId(),cartonId,line.detailId(),line.skuCode(),line.cargoName(),
|
||||
line.expected(),line.actual(),line.mismatch()?"ACCEPTED_VARIANCE":"MATCH",line.remark(),++lineNo);
|
||||
if(mapper.addCounted(taskId,operatorId,pallet==null?0:1,mismatch?1:0)!=1) throw conflict("任务清点数量已变化,请刷新后重试");
|
||||
TaskRow refreshed=requireTask(taskId,request.warehouseId());
|
||||
return countResult(refreshed,mapper.carton(taskId,cartonId));
|
||||
@@ -181,11 +183,14 @@ public class DropshipPalletizationService {
|
||||
}
|
||||
|
||||
private Pallet palletView(PalletRow row,String inboundCode){
|
||||
List<CartonRow> cartons=mapper.cartons(row.getId()); Map<String,List<CartonRow>> groups=new LinkedHashMap<>();
|
||||
for(CartonRow c:cartons) groups.computeIfAbsent(c.getInboundId()+"|"+c.getInboundDetailId()+"|"+c.getSkuCode(),ignored->new ArrayList<>()).add(c);
|
||||
List<SkuItem> items=groups.values().stream().map(values->{CartonRow first=values.get(0);return new SkuItem(first.getInboundId(),first.getInboundCode(),
|
||||
first.getInboundDetailId(),first.getSkuCode(),first.getCargoName(),values.size(),values.stream().mapToInt(v->v.getActualSkuQuantity()==null?0:v.getActualSkuQuantity()).sum(),
|
||||
values.stream().map(CartonRow::getBoxCode).toList());}).toList();
|
||||
List<CartonRow> cartons=mapper.cartons(row.getId()); Map<String,List<CartonSkuWithBox>> groups=new LinkedHashMap<>();
|
||||
for(CartonRow c:cartons) for(CartonSkuRow line:mapper.cartonSkus(c.getId()))
|
||||
groups.computeIfAbsent(c.getInboundId()+"|"+line.getInboundDetailId()+"|"+line.getSkuCode(),ignored->new ArrayList<>())
|
||||
.add(new CartonSkuWithBox(c,line));
|
||||
List<SkuItem> items=groups.values().stream().map(values->{CartonSkuWithBox first=values.get(0);return new SkuItem(first.carton().getInboundId(),first.carton().getInboundCode(),
|
||||
first.line().getInboundDetailId(),first.line().getSkuCode(),first.line().getCargoName(),values.size(),
|
||||
values.stream().mapToInt(v->v.line().getActualQuantity()==null?0:v.line().getActualQuantity()).sum(),
|
||||
values.stream().map(v->v.carton().getBoxCode()).toList());}).toList();
|
||||
int discrepancies=(int)cartons.stream().filter(c->"ACCEPTED_VARIANCE".equals(c.getDiscrepancyStatus())).count();
|
||||
PalletMetrics metrics=mapper.metrics(row.getId());
|
||||
return new Pallet(row.getId(),row.getPalletNo(),row.getPalletStatus(),cartons.size(),items.size(),discrepancies,
|
||||
@@ -194,12 +199,34 @@ public class DropshipPalletizationService {
|
||||
}
|
||||
|
||||
private Carton cartonView(CartonRow row,String inboundCode,boolean canCount,String blockReason){
|
||||
return new Carton(row.getId(),row.getBoxCode(),row.getInboundId(),inboundCode,row.getInboundDetailId(),row.getSkuCode(),row.getCargoName(),
|
||||
row.getExpectedSkuQuantity(),row.getActualSkuQuantity(),row.getDiscrepancyStatus(),row.getDiscrepancyRemark(),
|
||||
List<CartonSkuLine> lines=mapper.cartonSkus(row.getId()).stream().map(line -> new CartonSkuLine(line.getInboundDetailId(),
|
||||
line.getSkuCode(),line.getCargoName(),line.getExpectedQuantity(),line.getActualQuantity(),line.getDiscrepancyStatus(),line.getDiscrepancyRemark())).toList();
|
||||
return new Carton(row.getId(),row.getBoxCode(),row.getInboundId(),inboundCode,lines,
|
||||
row.getPalletId()==null?"COUNTED_UNBOUND":"BOUND",row.getPalletId(),row.getPalletNo(),canCount,blockReason);
|
||||
}
|
||||
private LabelRow requireLabel(long taskId,String boxCode){ LabelRow label=mapper.label(taskId,boxCode.trim()); if(label==null) throw new BizException(ResultCode.NOT_FOUND,"箱码不属于当前一件代发入仓单"); if(!StringUtils.hasText(label.getSkuCode())) throw conflict("箱码缺少SKU快照,不能清点"); requireExpectedQuantity(label); return label; }
|
||||
private int requireExpectedQuantity(LabelRow label){ if(label.getUnitsPerCarton()==null||label.getUnitsPerCarton()<1) throw conflict("箱码缺少每箱SKU数量快照,不能清点"); return label.getUnitsPerCarton(); }
|
||||
private LabelRow requireLabel(long taskId,String boxCode){ LabelRow label=mapper.label(taskId,boxCode.trim()); if(label==null) throw new BizException(ResultCode.NOT_FOUND,"箱码不属于当前一件代发入仓单"); if(labelSkuLines(taskId,label).isEmpty()) throw conflict("箱码缺少SKU快照,不能清点"); return label; }
|
||||
private List<LabelSkuRow> labelSkuLines(long taskId,LabelRow label){ return mapper.labelSkus(taskId,label.getCartonLabelId()); }
|
||||
private List<CountedLine> validateCountLines(List<LabelSkuRow> expected,List<CountSkuLine> actual){
|
||||
Map<Long,CountSkuLine> submitted=new LinkedHashMap<>();
|
||||
for(CountSkuLine line:actual) if(submitted.put(line.inboundDetailId(),line)!=null) throw invalid("同一SKU明细不能重复清点");
|
||||
if(submitted.size()!=expected.size()) throw invalid("必须清点当前箱内全部SKU");
|
||||
List<CountedLine> result=new ArrayList<>();
|
||||
for(LabelSkuRow line:expected){
|
||||
CountSkuLine input=submitted.get(line.getInboundDetailId());
|
||||
if(input==null || !Objects.equals(line.getSkuCode(),input.skuCode().trim())) throw invalid("清点SKU与箱唛不一致");
|
||||
boolean mismatch=!Objects.equals(line.getExpectedQuantity(),input.actualQuantity()); String remark=normalize(input.discrepancyRemark());
|
||||
if(!mismatch && input.discrepancyAction()!=DiscrepancyAction.MATCH) throw invalid("实收数量一致时差异动作必须为 MATCH");
|
||||
if(mismatch && input.discrepancyAction()!=DiscrepancyAction.ACCEPT_VARIANCE) throw conflict("实收数量存在差异,请确认差异处理结果");
|
||||
if(mismatch && !StringUtils.hasText(remark)) throw invalid("接受数量差异时必须填写处理说明");
|
||||
result.add(new CountedLine(line.getInboundDetailId(),line.getSkuCode(),line.getCargoName(),line.getExpectedQuantity(),input.actualQuantity(),remark,mismatch));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private boolean sameCountLines(List<CartonSkuRow> saved,List<CountSkuLine> submitted){
|
||||
if(saved.size()!=submitted.size()) return false;
|
||||
Map<Long,Integer> quantities=new HashMap<>(); for(CountSkuLine line:submitted) quantities.put(line.inboundDetailId(),line.actualQuantity());
|
||||
return saved.stream().allMatch(line->Objects.equals(line.getActualQuantity(),quantities.get(line.getInboundDetailId())));
|
||||
}
|
||||
private TaskRow requireWorkingTask(long taskId,long warehouseId,long operatorId){ TaskRow task=requireTask(taskId,warehouseId); if(!"CLAIMED".equals(task.getTaskStatus())) throw conflict("请先领取待清点组托任务"); if(!Objects.equals(task.getAssigneeId(),operatorId)) throw conflict("任务不属于当前操作员"); if(!"WAIT_PALLETIZE".equals(task.getProcessStatus())) throw conflict("入仓单已不在待清点组托状态"); requirePalletSetup(task); return task; }
|
||||
private void requirePalletSetup(TaskRow task){ if(!palletSetupReady(task)) throw conflict(palletSetupMessage(task)); }
|
||||
private boolean palletSetupReady(TaskRow task){ return task.getExpectedPalletCount()!=null && task.getExpectedPalletCount()>0 && mapper.palletCount(task.getId())==task.getExpectedPalletCount(); }
|
||||
@@ -211,4 +238,6 @@ public class DropshipPalletizationService {
|
||||
private static String normalize(String v){return StringUtils.hasText(v)?v.trim():null;}
|
||||
private static BizException conflict(String m){return new BizException(ResultCode.CONFLICT,m);}
|
||||
private static BizException invalid(String m){return new BizException(ResultCode.VALIDATION_FAILED,m);}
|
||||
private record CountedLine(long detailId,String skuCode,String cargoName,int expected,int actual,String remark,boolean mismatch){}
|
||||
private record CartonSkuWithBox(CartonRow carton,CartonSkuRow line){}
|
||||
}
|
||||
|
||||
+18
-11
@@ -25,6 +25,8 @@ import com.baiyue.firstleg.dto.DropshipPalletizationDtos.Pallet;
|
||||
@Service
|
||||
public class InboundCartonLabelPrintService {
|
||||
private static final BigDecimal PAGE_MM = new BigDecimal("100");
|
||||
private static final BigDecimal A4_WIDTH_MM = new BigDecimal("210");
|
||||
private static final BigDecimal A4_HEIGHT_MM = new BigDecimal("297");
|
||||
private static final List<String> SAFE_RESPONSE_HEADERS = List.of(
|
||||
HttpHeaders.CONTENT_DISPOSITION, HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA,
|
||||
"X-Print-Page-Count", "X-Request-Id", "X-Trace-Id");
|
||||
@@ -113,7 +115,7 @@ public class InboundCartonLabelPrintService {
|
||||
return ResponseEntity.status(response.getStatusCode()).headers(headers).body(response.getBody());
|
||||
}
|
||||
|
||||
/** 一件代发托盘标签:托盘号、入仓码、SKU箱数和托盘二维码。 */
|
||||
/** 一件代发 A4 托盘信息单:托盘号、Code 128 一维码和 SKU 归托明细。 */
|
||||
public ResponseEntity<byte[]> generateDropshipPallet(Pallet pallet, String inboundCode, String requestId) {
|
||||
if (pallet == null || !StringUtils.hasText(pallet.palletNo())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "托盘码尚未生成,不能打印");
|
||||
@@ -121,22 +123,27 @@ public class InboundCartonLabelPrintService {
|
||||
String inboundCodes = pallet.items().stream().map(item -> item.inboundCode())
|
||||
.filter(StringUtils::hasText).distinct().collect(java.util.stream.Collectors.joining("/"));
|
||||
if (!StringUtils.hasText(inboundCodes)) inboundCodes = inboundCode;
|
||||
String skuSummary = pallet.items().stream()
|
||||
.map(item -> item.skuCode() + "×" + item.cartonCount())
|
||||
.collect(java.util.stream.Collectors.joining(" / "));
|
||||
if (!StringUtils.hasText(skuSummary)) skuSummary = "待归托";
|
||||
PrintPdfClient.Label label = new PrintPdfClient.Label(pallet.palletNo(), inboundCodes,
|
||||
pallet.cartonCount() == 0 ? "CTNS:—" : "CTNS:" + pallet.cartonCount(), skuSummary, pallet.palletNo(), null);
|
||||
PrintPdfClient.InboundMarkPdfRequest request = new PrintPdfClient.InboundMarkPdfRequest(
|
||||
"INBOUND_MARK_V1", new PrintPdfClient.PageSpec(PAGE_MM, PAGE_MM),
|
||||
pallet.palletNo() + "-托盘唛.pdf", List.of(label));
|
||||
List<PrintPdfClient.DropshipPalletSkuRow> skuRows = pallet.items().stream()
|
||||
.map(item -> new PrintPdfClient.DropshipPalletSkuRow(item.inboundCode(), item.skuCode(),
|
||||
item.cargoName(), number(item.cartonCount()), number(item.skuQuantity())))
|
||||
.toList();
|
||||
PrintPdfClient.DropshipPalletDocument document = new PrintPdfClient.DropshipPalletDocument(
|
||||
pallet.palletNo(), inboundCodes, number(pallet.cartonCount()), number(pallet.skuCount()),
|
||||
pallet.weightKg(), pallet.volumeCbm(), pallet.palletNo(), skuRows);
|
||||
PrintPdfClient.DropshipPalletA4PdfRequest request = new PrintPdfClient.DropshipPalletA4PdfRequest(
|
||||
"DROPSHIP_PALLET_A4_V1", new PrintPdfClient.PageSpec(A4_WIDTH_MM, A4_HEIGHT_MM),
|
||||
pallet.palletNo() + "-A4托盘信息单.pdf", List.of(document));
|
||||
try {
|
||||
return requirePdf(printClient.generateInboundMarks(internalToken, requestId, request));
|
||||
return requirePdf(printClient.generateDropshipPalletA4Documents(internalToken, requestId, request));
|
||||
} catch (FeignException | NoFallbackAvailableException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "打印服务暂不可用,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
private int number(Integer value) {
|
||||
return value == null ? 0 : value;
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> generatePdf(String fileName, List<InboundCartonLabels.Label> labels,
|
||||
String requestId) {
|
||||
PrintPdfClient.InboundMarkPdfRequest request = new PrintPdfClient.InboundMarkPdfRequest(
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.baiyue.firstleg.dto.InboundCartonLabels;
|
||||
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundCartonLabelRepository;
|
||||
import com.baiyue.firstleg.mapper.InboundCartonLabelSkuMapper;
|
||||
import feign.FeignException;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
|
||||
@@ -24,6 +25,8 @@ import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -36,6 +39,7 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
private static final Set<String> ALLOCATABLE_STATUSES = Set.of("PREPARING", "WAIT_MEASURE", "MEASURING");
|
||||
|
||||
@Resource private InboundCartonLabelRepository repository;
|
||||
@Resource private InboundCartonLabelSkuMapper labelSkus;
|
||||
@Resource private OrderCargoClient orderCargoClient;
|
||||
@Resource private PrintableOrderInboundClient printableOrderInboundClient;
|
||||
@Resource private InboundOrderCreationService inboundOrderCreationService;
|
||||
@@ -76,7 +80,8 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
if (existingFingerprint.equals(fingerprint)) return batch(inbound, consistent);
|
||||
return batch(inbound, consistent);
|
||||
}
|
||||
int total = cargoes.stream().mapToInt(this::requirePackageCount).sum();
|
||||
List<CargoGroup> groups = cartonGroups(cargoes, inbound.getFulfillmentType());
|
||||
int total = groups.stream().mapToInt(CargoGroup::packageCount).sum();
|
||||
if (total < 1 || total > MAX_LABELS) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "唛头箱数必须为1至2000箱");
|
||||
}
|
||||
@@ -86,12 +91,20 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
|
||||
int cartonIndex = 0;
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (OrderCargoClient.CargoLabelBinding cargo : cargoes) {
|
||||
int detailCount = requirePackageCount(cargo);
|
||||
for (CargoGroup group : groups) {
|
||||
OrderCargoClient.CargoLabelBinding cargo = group.cargoes().get(0);
|
||||
int detailCount = group.packageCount();
|
||||
for (int offset = 0; offset < detailCount; offset++) {
|
||||
cartonIndex++;
|
||||
InboundCartonLabelEntity label = label(inbound, cargo, total, cartonIndex, fingerprint, now);
|
||||
repository.insert(label);
|
||||
if ("ONE_CLICK_DROPSHIP".equals(inbound.getFulfillmentType())) {
|
||||
int lineNo = 0;
|
||||
for (OrderCargoClient.CargoLabelBinding line : group.cargoes()) {
|
||||
labelSkus.insert(IdWorker.getId(), inbound.getTenantId(), inbound.getId(), label.getId(),
|
||||
line.cargoId(), line.skuNo().trim(), line.cargoName(), line.unitsPerCarton(), ++lineNo);
|
||||
}
|
||||
}
|
||||
existing.add(label);
|
||||
}
|
||||
}
|
||||
@@ -195,6 +208,27 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
return cargo.packageCount();
|
||||
}
|
||||
|
||||
private List<CargoGroup> cartonGroups(List<OrderCargoClient.CargoLabelBinding> cargoes, String fulfillmentType) {
|
||||
if (!"ONE_CLICK_DROPSHIP".equals(fulfillmentType)) {
|
||||
return cargoes.stream().map(cargo -> new CargoGroup(null, requirePackageCount(cargo), List.of(cargo))).toList();
|
||||
}
|
||||
Map<String, List<OrderCargoClient.CargoLabelBinding>> grouped = new LinkedHashMap<>();
|
||||
int single = 0;
|
||||
for (OrderCargoClient.CargoLabelBinding cargo : cargoes) {
|
||||
requirePackageCount(cargo);
|
||||
String group = StringUtils.hasText(cargo.packingGroupNo()) ? cargo.packingGroupNo().trim() : "__single_" + (++single);
|
||||
grouped.computeIfAbsent(group, ignored -> new ArrayList<>()).add(cargo);
|
||||
}
|
||||
return grouped.entrySet().stream().map(entry -> {
|
||||
List<OrderCargoClient.CargoLabelBinding> lines = entry.getValue();
|
||||
int count = lines.get(0).packageCount();
|
||||
if (lines.stream().anyMatch(line -> line.packageCount() != count)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "同一混箱组合的箱数必须一致:" + entry.getKey());
|
||||
}
|
||||
return new CargoGroup(entry.getKey(), count, List.copyOf(lines));
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private void requirePrintableInboundCode(String inboundCode, int total) {
|
||||
if (!StringUtils.hasText(inboundCode) || !inboundCode.matches("[A-Za-z0-9._:-]+")) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "入仓码不能生成可打印二维码");
|
||||
@@ -276,6 +310,7 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
cargo.cargoName() == null ? "" : cargo.cargoName().trim(),
|
||||
cargo.skuNo() == null ? "" : cargo.skuNo().trim(), String.valueOf(cargo.packageCount()),
|
||||
String.valueOf(cargo.unitsPerCarton()),
|
||||
cargo.packingGroupNo() == null ? "" : cargo.packingGroupNo().trim(),
|
||||
decimalOrEmpty(cargo.lengthCm()), decimalOrEmpty(cargo.widthCm()), decimalOrEmpty(cargo.heightCm())))
|
||||
.collect(java.util.stream.Collectors.joining("\n"));
|
||||
try {
|
||||
@@ -296,11 +331,16 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
|
||||
private InboundCartonLabels.Batch batch(InboundOrderEntity inbound,
|
||||
List<InboundCartonLabelEntity> entities) {
|
||||
List<InboundCartonLabelSkuMapper.SkuRow> skuRows = labelSkus.findByInbound(inbound.getTenantId(), inbound.getId());
|
||||
Map<Long, List<InboundCartonLabelSkuMapper.SkuRow>> skuRowsByLabel = skuRows == null ? Map.of()
|
||||
: skuRows.stream().collect(java.util.stream.Collectors.groupingBy(
|
||||
InboundCartonLabelSkuMapper.SkuRow::getCartonLabelId, LinkedHashMap::new,
|
||||
java.util.stream.Collectors.toList()));
|
||||
List<InboundCartonLabels.Label> labels = entities.stream().map(entity ->
|
||||
new InboundCartonLabels.Label(entity.getId(), entity.getInboundDetailId(), entity.getCargoItemNo(),
|
||||
entity.getCargoName(), entity.getDetailPackageCount(), entity.getBoxCode(),
|
||||
entity.getTotalCartonCount(), entity.getCartonIndex(), entity.getBoxSpecification(),
|
||||
entity.getLengthCm(), entity.getWidthCm(), entity.getHeightCm(), entity.getSkuCode(), inbound.getInboundCode(),
|
||||
entity.getLengthCm(), entity.getWidthCm(), entity.getHeightCm(), displaySkuCodes(entity, skuRowsByLabel), inbound.getInboundCode(),
|
||||
fallback(inbound.getWarehouseName(), "WAREHOUSE"),
|
||||
"CTNS:" + entity.getTotalCartonCount() + "-" + entity.getCartonIndex(),
|
||||
fallback(inbound.getChannelName(), fallback(inbound.getChannelCode(), "ROUTE"))))
|
||||
@@ -314,9 +354,20 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
return StringUtils.hasText(value) ? value.trim() : fallback;
|
||||
}
|
||||
|
||||
private String displaySkuCodes(InboundCartonLabelEntity label,
|
||||
Map<Long, List<InboundCartonLabelSkuMapper.SkuRow>> skuRowsByLabel) {
|
||||
List<InboundCartonLabelSkuMapper.SkuRow> lines = skuRowsByLabel.get(label.getId());
|
||||
if (lines == null || lines.isEmpty()) return label.getSkuCode();
|
||||
return lines.stream().map(InboundCartonLabelSkuMapper.SkuRow::getSkuCode)
|
||||
.filter(StringUtils::hasText).distinct().collect(java.util.stream.Collectors.joining(" / "));
|
||||
}
|
||||
|
||||
private void requireIds(long tenantId, long inboundId) {
|
||||
if (tenantId <= 0 || inboundId <= 0) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "客户ID和入库单ID必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
private record CargoGroup(String groupNo, int packageCount,
|
||||
List<OrderCargoClient.CargoLabelBinding> cargoes) {}
|
||||
}
|
||||
|
||||
@@ -33,3 +33,19 @@ CREATE TABLE IF NOT EXISTS fl_inbound_carton_label (
|
||||
KEY idx_carton_label_detail (tenant_id, inbound_id, inbound_detail_id),
|
||||
CONSTRAINT fk_carton_label_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
|
||||
) ENGINE=InnoDB COMMENT='首次打印前冻结的实际箱号与订单货物明细映射';
|
||||
CREATE TABLE IF NOT EXISTS fl_inbound_carton_label_sku (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '箱唛SKU快照ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
||||
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
|
||||
carton_label_id BIGINT UNSIGNED NOT NULL COMMENT '物理箱唛ID',
|
||||
inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '订单货物明细ID',
|
||||
sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码快照',
|
||||
cargo_name VARCHAR(255) NULL COMMENT '货物名称快照',
|
||||
expected_quantity INT UNSIGNED NOT NULL COMMENT '该物理箱内应收SKU数量',
|
||||
line_no INT UNSIGNED NOT NULL COMMENT '箱内SKU展示顺序',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_carton_label_sku_detail (carton_label_id,inbound_detail_id),
|
||||
KEY idx_carton_label_sku_code (tenant_id,inbound_id,sku_code),
|
||||
CONSTRAINT fk_carton_label_sku_label FOREIGN KEY (carton_label_id) REFERENCES fl_inbound_carton_label(id)
|
||||
) ENGINE=InnoDB COMMENT='物理箱唛内的多SKU冻结快照';
|
||||
|
||||
@@ -230,6 +230,23 @@ CREATE TABLE IF NOT EXISTS fl_inbound_carton_label (
|
||||
KEY idx_carton_label_detail (tenant_id, inbound_id, inbound_detail_id),
|
||||
CONSTRAINT fk_carton_label_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
|
||||
) ENGINE=InnoDB COMMENT='首次打印前冻结的实际箱号与订单货物明细映射';
|
||||
CREATE TABLE IF NOT EXISTS fl_inbound_carton_label_sku (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '箱唛SKU快照ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
||||
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
|
||||
carton_label_id BIGINT UNSIGNED NOT NULL COMMENT '物理箱唛ID',
|
||||
inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '订单货物明细ID',
|
||||
sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码快照',
|
||||
cargo_name VARCHAR(255) NULL COMMENT '货物名称快照',
|
||||
expected_quantity INT UNSIGNED NOT NULL COMMENT '该物理箱内应收SKU数量',
|
||||
line_no INT UNSIGNED NOT NULL COMMENT '箱内SKU展示顺序',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_carton_label_sku_detail (carton_label_id,inbound_detail_id),
|
||||
KEY idx_carton_label_sku_code (tenant_id,inbound_id,sku_code),
|
||||
CONSTRAINT fk_carton_label_sku_label FOREIGN KEY (carton_label_id) REFERENCES fl_inbound_carton_label(id)
|
||||
) ENGINE=InnoDB COMMENT='物理箱唛内的多SKU冻结快照';
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_inbound_measurement (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '测量批次ID',
|
||||
@@ -1030,3 +1047,19 @@ CREATE TABLE IF NOT EXISTS fl_dropship_pallet_carton (
|
||||
CONSTRAINT fk_dropship_carton_pallet FOREIGN KEY (pallet_id) REFERENCES fl_dropship_pallet(id),
|
||||
CONSTRAINT fk_dropship_carton_label FOREIGN KEY (carton_label_id) REFERENCES fl_inbound_carton_label(id)
|
||||
) ENGINE=InnoDB COMMENT='一件代发托盘内逐箱SKU和入仓单明细';
|
||||
CREATE TABLE IF NOT EXISTS fl_dropship_pallet_carton_sku (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '清点箱SKU明细ID',
|
||||
carton_id BIGINT UNSIGNED NOT NULL COMMENT '清点物理箱ID',
|
||||
inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '入仓货物明细ID',
|
||||
sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码快照',
|
||||
cargo_name VARCHAR(255) NULL COMMENT '货物名称快照',
|
||||
expected_quantity INT UNSIGNED NOT NULL COMMENT '应收数量',
|
||||
actual_quantity INT UNSIGNED NOT NULL COMMENT '实收数量',
|
||||
discrepancy_status VARCHAR(24) NOT NULL COMMENT 'MATCH或ACCEPTED_VARIANCE',
|
||||
discrepancy_remark VARCHAR(500) NULL COMMENT '差异处理说明',
|
||||
line_no INT UNSIGNED NOT NULL COMMENT '箱内SKU顺序',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_dropship_carton_sku_detail (carton_id,inbound_detail_id),
|
||||
KEY idx_dropship_carton_sku_code (sku_code,carton_id),
|
||||
CONSTRAINT fk_dropship_carton_sku_carton FOREIGN KEY (carton_id) REFERENCES fl_dropship_pallet_carton(id)
|
||||
) ENGINE=InnoDB COMMENT='一件代发物理箱内逐SKU清点事实';
|
||||
|
||||
@@ -67,15 +67,15 @@ paths:
|
||||
responses: {'200': {description: OK}}
|
||||
/api/platform/v1/first-leg/pda-dropship/tasks/{taskId}/pallets/{palletId}/print:
|
||||
get:
|
||||
summary: PDA 打印预生成的一件代发托盘码
|
||||
description: 测量审核通过并生成托盘码后即可打印,空托打印“待归托”。仅当前仓库、当前操作员可访问。返回 100mm × 100mm PDF。
|
||||
summary: PDA 获取预生成托盘码的 A4 托盘信息单
|
||||
description: 测量审核通过并生成托盘码后即可获取;每个托盘固定一页 A4,包含托盘号及其 Code 128 一维码、入仓码、箱数以及已归托 SKU、商品名称和实点数量,不展示作业状态。空托显示暂无已归托 SKU。仅当前仓库、当前操作员可访问。
|
||||
x-required-authority: platform:firstleg:pda:view
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/TaskId'}
|
||||
- {name: palletId, in: path, required: true, schema: {type: integer, format: int64}}
|
||||
- {$ref: '#/components/parameters/WarehouseId'}
|
||||
responses:
|
||||
'200': {description: 托盘码 PDF, content: {application/pdf: {schema: {type: string, format: binary}}}}
|
||||
'200': {description: A4 托盘信息单 PDF, content: {application/pdf: {schema: {type: string, format: binary}}}}
|
||||
/api/platform/v1/first-leg/pda-dropship/inbound-orders/{inboundId}/pallets:
|
||||
get:
|
||||
summary: Admin 查询入仓单权威托盘结果
|
||||
@@ -83,7 +83,7 @@ paths:
|
||||
responses: {'200': {description: OK}}
|
||||
/api/platform/v1/first-leg/pda-dropship/inbound-orders/{inboundId}/pallets/{palletId}/print:
|
||||
get:
|
||||
summary: 打印托盘码、入仓码和 SKU 实收汇总
|
||||
summary: 获取包含托盘码、入仓码和 SKU 实收汇总的 A4 托盘信息单
|
||||
x-required-authority: firstleg:inbound:list
|
||||
responses: {'200': {description: PDF}}
|
||||
components:
|
||||
@@ -114,18 +114,23 @@ components:
|
||||
boxCode: {type: string}
|
||||
inboundId: {type: integer, format: int64}
|
||||
inboundCode: {type: string}
|
||||
inboundDetailId: {type: integer, format: int64}
|
||||
skuCode: {type: string}
|
||||
cargoName: {type: string, nullable: true}
|
||||
expectedSkuQuantity: {type: integer, minimum: 1}
|
||||
actualSkuQuantity: {type: integer, minimum: 1, nullable: true}
|
||||
discrepancyStatus: {type: string, enum: [MATCH, ACCEPTED_VARIANCE], nullable: true}
|
||||
discrepancyRemark: {type: string, nullable: true}
|
||||
skuLines: {type: array, minItems: 1, items: {$ref: '#/components/schemas/CartonSkuLine'}, description: 当前物理箱内全部SKU清点行}
|
||||
countStatus: {type: string, enum: [UNCOUNTED, COUNTED_UNBOUND, BOUND]}
|
||||
palletId: {type: integer, format: int64, nullable: true}
|
||||
palletNo: {$ref: '#/components/schemas/NullablePalletNo'}
|
||||
canCount: {type: boolean}
|
||||
blockReason: {type: string, nullable: true}
|
||||
CartonSkuLine:
|
||||
type: object
|
||||
required: [inboundDetailId, skuCode, expectedQuantity]
|
||||
properties:
|
||||
inboundDetailId: {type: integer, format: int64}
|
||||
skuCode: {type: string}
|
||||
cargoName: {type: string, nullable: true}
|
||||
expectedQuantity: {type: integer, minimum: 1}
|
||||
actualQuantity: {type: integer, minimum: 0, nullable: true}
|
||||
discrepancyStatus: {type: string, enum: [MATCH, ACCEPTED_VARIANCE], nullable: true}
|
||||
discrepancyRemark: {type: string, nullable: true}
|
||||
Pallet:
|
||||
type: object
|
||||
properties:
|
||||
@@ -147,14 +152,23 @@ components:
|
||||
properties: {warehouseId: {type: integer, format: int64}}
|
||||
CountCartonRequest:
|
||||
type: object
|
||||
required: [warehouseId, boxCode, actualSkuQuantity, discrepancyAction, operationId]
|
||||
required: [warehouseId, boxCode, skuLines, operationId]
|
||||
properties:
|
||||
warehouseId: {type: integer, format: int64}
|
||||
boxCode: {type: string, maxLength: 80}
|
||||
palletNo: {$ref: '#/components/schemas/NullablePalletNo'}
|
||||
actualSkuQuantity: {type: integer, minimum: 1}
|
||||
discrepancyAction: {type: string, enum: [MATCH, ACCEPT_VARIANCE]}
|
||||
discrepancyRemark: {type: string, maxLength: 500, nullable: true}
|
||||
skuLines:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: object
|
||||
required: [inboundDetailId, skuCode, actualQuantity, discrepancyAction]
|
||||
properties:
|
||||
inboundDetailId: {type: integer, format: int64}
|
||||
skuCode: {type: string, maxLength: 128}
|
||||
actualQuantity: {type: integer, minimum: 0}
|
||||
discrepancyAction: {type: string, enum: [MATCH, ACCEPT_VARIANCE]}
|
||||
discrepancyRemark: {type: string, maxLength: 500, nullable: true}
|
||||
operationId: {type: string, maxLength: 128}
|
||||
BindPendingRequest:
|
||||
type: object
|
||||
|
||||
+12
-5
@@ -3,12 +3,15 @@ package com.baiyue.firstleg.service;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.CountCartonRequest;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.DiscrepancyAction;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.CountSkuLine;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.LabelRow;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.CartonRow;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.PalletRow;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.TaskRow;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.LabelSkuRow;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.CartonSkuRow;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
@@ -20,7 +23,7 @@ class DropshipPalletizationServiceTest {
|
||||
private final FirstlegDocumentNumberGenerator numbers=mock(FirstlegDocumentNumberGenerator.class);
|
||||
private final DropshipPalletizationMapper mapper=mock(DropshipPalletizationMapper.class);
|
||||
private final DropshipPalletizationService service=new DropshipPalletizationService();
|
||||
DropshipPalletizationServiceTest(){ ReflectionTestUtils.setField(service,"mapper",mapper); ReflectionTestUtils.setField(service,"documentNumbers",numbers); }
|
||||
DropshipPalletizationServiceTest(){ ReflectionTestUtils.setField(service,"mapper",mapper); ReflectionTestUtils.setField(service,"documentNumbers",numbers); when(mapper.labelSkus(anyLong(),anyLong())).thenAnswer(call -> java.util.List.of(labelSku())); when(mapper.cartonSkus(anyLong())).thenAnswer(call -> java.util.List.of(cartonSku())); }
|
||||
|
||||
@Test void reviewCreatesExpectedNumberOfPrintablePalletCodes(){
|
||||
InboundOrderEntity inbound=dropshipInbound(); when(mapper.insertTask(anyLong(),eq(1L),eq(30L),eq(100L),eq(8))).thenReturn(1);
|
||||
@@ -38,20 +41,21 @@ class DropshipPalletizationServiceTest {
|
||||
@Test void resolvesUncountedCartonBeforeQuantityConfirmation(){
|
||||
when(mapper.task(10L,100L)).thenReturn(task()); when(mapper.palletCount(10L)).thenReturn(2); when(mapper.label(10L,"BOX-1")).thenReturn(label());
|
||||
var result=service.resolveCarton(10L,100L,"BOX-1",7L);
|
||||
assertThat(result.inboundCode()).isEqualTo("IN-1"); assertThat(result.expectedSkuQuantity()).isEqualTo(12); assertThat(result.canCount()).isTrue();
|
||||
assertThat(result.inboundCode()).isEqualTo("IN-1"); assertThat(result.skuLines()).extracting("expectedQuantity").containsExactly(12); assertThat(result.canCount()).isTrue();
|
||||
}
|
||||
|
||||
@Test void cartonCanBindDirectlyToAPreGeneratedPallet(){
|
||||
when(mapper.task(10L,100L)).thenReturn(task()); when(mapper.palletCount(10L)).thenReturn(2); when(mapper.label(10L,"BOX-1")).thenReturn(label()); when(mapper.palletByNo("DP-260911-0001")).thenReturn(pallet());
|
||||
when(mapper.addCounted(10L,7L,1,0)).thenReturn(1); when(mapper.carton(eq(10L),anyLong())).thenReturn(carton());
|
||||
when(mapper.pallet(10L,20L,100L)).thenReturn(pallet()); when(mapper.cartons(20L)).thenReturn(java.util.List.of());
|
||||
service.countCarton(10L,new CountCartonRequest(100L,"BOX-1",12,DiscrepancyAction.MATCH,null,"DP-260911-0001","op-1"),7L,"张三");
|
||||
service.countCarton(10L,countRequest("DP-260911-0001"),7L,"张三");
|
||||
verify(mapper).insertCarton(anyLong(),eq(10L),eq(20L),eq(1L),eq(30L),eq(40L),eq(50L),eq("SKU-1"),any(),eq("BOX-1"),eq(12),eq(12),eq("MATCH"),isNull(),eq("op-1"),eq(7L),eq("张三"));
|
||||
verify(mapper).insertCartonSku(anyLong(),anyLong(),eq(40L),eq("SKU-1"),any(),eq(12),eq(12),eq("MATCH"),isNull(),eq(1));
|
||||
}
|
||||
|
||||
@Test void unknownPalletCodeCannotCreateAnAdHocPallet(){
|
||||
when(mapper.task(10L,100L)).thenReturn(task()); when(mapper.palletCount(10L)).thenReturn(2); when(mapper.label(10L,"BOX-1")).thenReturn(label());
|
||||
var request=new CountCartonRequest(100L,"BOX-1",12,DiscrepancyAction.MATCH,null,"DP-260911-9999","op-1");
|
||||
var request=countRequest("DP-260911-9999");
|
||||
assertThatThrownBy(()->service.countCarton(10L,request,7L,"张三")).isInstanceOf(BizException.class).hasMessageContaining("测量审核后生成");
|
||||
verify(mapper,never()).insertPallet(anyLong(),anyLong(),anyLong(),anyLong(),anyLong(),anyString(),anyString(),anyLong(),anyString());
|
||||
}
|
||||
@@ -61,12 +65,15 @@ class DropshipPalletizationServiceTest {
|
||||
when(mapper.task(10L,100L)).thenReturn(task()); when(mapper.palletCount(10L)).thenReturn(0); when(mapper.label(10L,"BOX-1")).thenReturn(label());
|
||||
var result=service.resolveCarton(10L,100L,"BOX-1",7L);
|
||||
assertThat(result.canCount()).isFalse(); assertThat(result.blockReason()).contains("预计 2 托").contains("当前仅生成 0 个托盘码");
|
||||
assertThatThrownBy(()->service.countCarton(10L,new CountCartonRequest(100L,"BOX-1",12,DiscrepancyAction.MATCH,null,null,"op-1"),7L,"张三"))
|
||||
assertThatThrownBy(()->service.countCarton(10L,countRequest(null),7L,"张三"))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("预计 2 托");
|
||||
}
|
||||
|
||||
private static TaskRow task(){TaskRow t=new TaskRow();t.setId(10L);t.setTenantId(1L);t.setInboundId(30L);t.setWarehouseId(100L);t.setInboundCode("IN-1");t.setTaskStatus("CLAIMED");t.setAssigneeId(7L);t.setProcessStatus("WAIT_PALLETIZE");t.setTotalCartons(2);t.setCountedCartons(0);t.setAllocatedCartons(0);t.setDiscrepancyCartons(0);t.setExpectedPalletCount(2);t.setVersion(1);return t;}
|
||||
private static LabelRow label(){LabelRow l=new LabelRow();l.setCartonLabelId(50L);l.setInboundDetailId(40L);l.setSkuCode("SKU-1");l.setCargoName("货物");l.setUnitsPerCarton(12);l.setInboundCode("IN-1");l.setBoxCode("BOX-1");return l;}
|
||||
private static LabelSkuRow labelSku(){LabelSkuRow l=new LabelSkuRow();l.setInboundDetailId(40L);l.setSkuCode("SKU-1");l.setCargoName("货物");l.setExpectedQuantity(12);l.setLineNo(1);return l;}
|
||||
private static CartonSkuRow cartonSku(){CartonSkuRow l=new CartonSkuRow();l.setInboundDetailId(40L);l.setSkuCode("SKU-1");l.setCargoName("货物");l.setExpectedQuantity(12);l.setActualQuantity(12);l.setDiscrepancyStatus("MATCH");l.setLineNo(1);return l;}
|
||||
private static CountCartonRequest countRequest(String palletNo){return new CountCartonRequest(100L,"BOX-1",java.util.List.of(new CountSkuLine(40L,"SKU-1",12,DiscrepancyAction.MATCH,null)),palletNo,"op-1");}
|
||||
private static PalletRow pallet(){PalletRow p=new PalletRow();p.setId(20L);p.setTaskId(10L);p.setTenantId(1L);p.setInboundId(30L);p.setWarehouseId(100L);p.setPalletNo("DP-260911-0001");p.setPalletStatus("OPEN");p.setVersion(1);return p;}
|
||||
private static CartonRow carton(){CartonRow c=new CartonRow();c.setId(60L);c.setTaskId(10L);c.setPalletId(20L);c.setInboundId(30L);c.setInboundDetailId(40L);c.setPalletNo("DP-260911-0001");c.setSkuCode("SKU-1");c.setCargoName("货物");c.setBoxCode("BOX-1");c.setExpectedSkuQuantity(12);c.setActualSkuQuantity(12);c.setDiscrepancyStatus("MATCH");return c;}
|
||||
}
|
||||
|
||||
+19
-1
@@ -42,13 +42,31 @@ class InboundCartonLabelPrintServiceTest {
|
||||
Pallet open = dropshipPallet("OPEN");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_PDF);
|
||||
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
|
||||
when(print.generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.eq("request-open"), org.mockito.ArgumentMatchers.any()))
|
||||
.thenReturn(new ResponseEntity<>(new byte[] {7, 8, 9}, headers,
|
||||
org.springframework.http.HttpStatus.OK));
|
||||
|
||||
ResponseEntity<byte[]> response = service.generateDropshipPallet(open, "PCHK2608040174", "request-open");
|
||||
assertThat(response.getBody()).containsExactly(7, 8, 9);
|
||||
|
||||
ArgumentCaptor<PrintPdfClient.DropshipPalletA4PdfRequest> request =
|
||||
ArgumentCaptor.forClass(PrintPdfClient.DropshipPalletA4PdfRequest.class);
|
||||
verify(print).generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.eq("request-open"), request.capture());
|
||||
assertThat(request.getValue().templateCode()).isEqualTo("DROPSHIP_PALLET_A4_V1");
|
||||
assertThat(request.getValue().pageSpec().widthMm()).isEqualByComparingTo("210");
|
||||
assertThat(request.getValue().pageSpec().heightMm()).isEqualByComparingTo("297");
|
||||
assertThat(request.getValue().documents()).singleElement().satisfies(document -> {
|
||||
assertThat(document.palletNo()).isEqualTo("DP-260911-0001");
|
||||
assertThat(document.inboundCodes()).isEqualTo("PCHK2608040174");
|
||||
assertThat(document.skuRows()).singleElement().satisfies(row -> {
|
||||
assertThat(row.skuCode()).isEqualTo("SKU-1");
|
||||
assertThat(row.cargoName()).isEqualTo("音箱");
|
||||
assertThat(row.cartonCount()).isEqualTo(1);
|
||||
assertThat(row.skuQuantity()).isEqualTo(19);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.baiyue.firstleg.client.PrintableOrderInboundClient;
|
||||
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundCartonLabelRepository;
|
||||
import com.baiyue.firstleg.mapper.InboundCartonLabelSkuMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -32,6 +33,7 @@ class InboundCartonLabelServiceTest {
|
||||
@Mock private OrderCargoClient orderCargoClient;
|
||||
@Mock private PrintableOrderInboundClient printableOrderInboundClient;
|
||||
@Mock private InboundOrderCreationService inboundOrderCreationService;
|
||||
@Mock private InboundCartonLabelSkuMapper labelSkus;
|
||||
private InboundCartonLabelService service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -41,6 +43,7 @@ class InboundCartonLabelServiceTest {
|
||||
ReflectionTestUtils.setField(service, "orderCargoClient", orderCargoClient);
|
||||
ReflectionTestUtils.setField(service, "printableOrderInboundClient", printableOrderInboundClient);
|
||||
ReflectionTestUtils.setField(service, "inboundOrderCreationService", inboundOrderCreationService);
|
||||
ReflectionTestUtils.setField(service, "labelSkus", labelSkus);
|
||||
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
|
||||
}
|
||||
|
||||
@@ -75,6 +78,26 @@ class InboundCartonLabelServiceTest {
|
||||
verify(repository).updatePackageCount(10001L, 70001L, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mixedCartonGroupCreatesOnePhysicalBoxSeriesWithEverySkuLine() {
|
||||
InboundOrderEntity inbound = inbound();
|
||||
inbound.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
when(repository.lockInbound(10001L, 70001L)).thenReturn(inbound);
|
||||
when(repository.findActiveByInbound(10001L, 70001L)).thenReturn(new ArrayList<>());
|
||||
when(repository.findByInbound(10001L, 70001L)).thenReturn(new ArrayList<>());
|
||||
var skuA = mixedCargo(80001L, 1, "SKU-A", 2, 3, "A");
|
||||
var skuB = mixedCargo(80002L, 2, "SKU-B", 5, 3, "A");
|
||||
when(orderCargoClient.listForCartonLabels("internal-token", 10001L, 60001L))
|
||||
.thenReturn(ApiResponse.success(List.of(skuA, skuB)));
|
||||
|
||||
var batch = service.getOrCreate(10001L, 70001L);
|
||||
|
||||
assertThat(batch.totalCount()).isEqualTo(3);
|
||||
verify(labelSkus, org.mockito.Mockito.times(6)).insert(any(Long.class),
|
||||
org.mockito.ArgumentMatchers.eq(10001L), org.mockito.ArgumentMatchers.eq(70001L), any(Long.class),
|
||||
any(Long.class), any(String.class), any(), any(Integer.class), any(Integer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatedAndPartialPrintsReadFrozenMappingWithoutReallocation() {
|
||||
InboundOrderEntity inbound = inbound();
|
||||
@@ -283,6 +306,12 @@ class InboundCartonLabelServiceTest {
|
||||
new BigDecimal(length), new BigDecimal(width), new BigDecimal(height));
|
||||
}
|
||||
|
||||
private OrderCargoClient.CargoLabelBinding mixedCargo(long id, int itemNo, String sku,
|
||||
int units, int count, String group) {
|
||||
return new OrderCargoClient.CargoLabelBinding(id, 60001L, itemNo, "货物" + itemNo, sku, count,
|
||||
units, new BigDecimal("60"), new BigDecimal("40"), new BigDecimal("30"), null, group);
|
||||
}
|
||||
|
||||
private List<InboundCartonLabelEntity> labels(InboundOrderEntity inbound, int total) {
|
||||
List<InboundCartonLabelEntity> values = new ArrayList<>();
|
||||
for (int index = 1; index <= total; index++) {
|
||||
|
||||
Reference in New Issue
Block a user