提交
This commit is contained in:
@@ -60,7 +60,7 @@
|
||||
| 海外采购、客户下单、采购审核、客户/采购仓候选与操作流水 | `procurement/controller/`、`procurement/service/OverseasProcurementService.java`;客户入口按 `merchant:cross-border:procurement:list/create` 拆分且只取 Token 客户范围;OpenAPI `src/main/resources/openapi/overseas-business-v1.yaml` |
|
||||
| 海外业务资金指令与结果 | `wallet/WalletCommandOutboxService.java`、`WalletCommandCompletedConsumer.java`、`WalletResultHandler.java`;采购、换标、人工海外费用和仓租通过 `WALLET_COMMAND_REQUESTED` 请求 Finance 冻结、结算或释放,并以 `WALLET_COMMAND_COMPLETED` 幂等回写业务资金状态;不得同步调用 Finance 钱包或在 Lastleg 自建余额账本 |
|
||||
| 海外业务费用来源 | `financefee/controller/PlatformOverseasFeeSourceController.java`、`financefee/service/OverseasManualFeeService.java`、`OverseasFeeSourceService.java`、`OverseasFeeBusinessGuard.java`、`OverseasFeeOutboxService.java`;费用必须匹配当前客户的真实服务单,公共费用目录通过 Firstleg 内部接口读取。头程附加费审核后进入账单;海外人工应收创建先冻结、审核后扣款,驳回或取消释放,扣款成功后仅登记本地已收来源,不再推送财务账单;采购、换标原业务收费仍按其独立资金身份处理。头程账单费用及供应商应付经直接 Outbox 投递 Finance,余额应收仅通过资金指令取得扣款回执,OpenAPI `src/main/resources/openapi/overseas-finance-fees-v1.yaml`。新菜单列表权限与财务编辑、审核按钮权限分离;本地检查不能代替真实资金、账单联调 |
|
||||
| 自动仓租及出库资金门禁 | `storagerent/`;实际 PDA 收货建立原计租批次,头程自有海外仓目的地与仓储转运用中转阶梯,退货用原托阶梯;每24小时进一天,中转部分出库按原批次份额计费,退货原托余一箱仍按整托计费,同托同计租日不重复收取,余货不重置库龄。退货已计费截止日与实际出库、费用生成同事务推进,后续只收新增库龄;并发推进后旧预占必须释放重建。实际出库命令前单独提交资金预校验,冻结成功后才允许扣库存并生成待审核仓租,订单费用与海外费用管理展示实际入库、出库、原计算额及实扣额;审核前可调价且说明必填,批准后沿原冻结按实额结算,驳回释放冻结;全部仓租使用余额及授信。客户仓租配置优先,无配置按报价表默认,内部查询失败不得回退。预占保存配置版本、完整阶梯、USD/CNY 原币及结算汇率快照,实际扣款不读最新价格。发布及真实验收清单见 `docs/overseas-fees-storage-rent-release.md` |
|
||||
| 自动仓租及出库资金门禁 | `storagerent/`;实际 PDA 收货建立原计租批次,头程自有海外仓目的地与仓储转运用中转阶梯,退货用原托阶梯;每24小时进一天,头程无逐箱测量时按原批次份额计费;仓储转运的排车、正常出库及废弃按实际箱长宽高汇总CBM计费,部分出库需明确箱唛,缺尺寸或来源不符则阻断,冻结快照保存实箱与体积;退货原托余一箱仍按整托计费,同托同计租日不重复收取,余货不重置库龄。退货已计费截止日与实际出库、费用生成同事务推进,后续只收新增库龄;并发推进后旧预占必须释放重建。实际出库命令前单独提交资金预校验,冻结成功后才允许扣库存并生成待审核仓租,订单费用与海外费用管理展示实际入库、出库、原计算额及实扣额;审核前可调价且说明必填,批准后沿原冻结按实额结算,驳回释放冻结;全部仓租使用余额及授信。客户仓租配置优先,无配置按报价表默认,内部查询失败不得回退。预占保存配置版本、完整阶梯、USD/CNY 原币及结算汇率快照,实际扣款不读最新价格。发布及真实验收清单见 `docs/overseas-fees-storage-rent-release.md` |
|
||||
| 美国/本土卡车订单 | `trucking/controller/PlatformUsTruckOrderController.java`、`MerchantUsTruckOrderController.java` 及同子域 Service/DTO;客户入口按 `merchant:cross-border:domestic-trucking:list/create/edit` 拆分 |
|
||||
| 本土散板订单 | `looseboard/controller/PlatformLocalLooseboardController.java`、`MerchantLocalLooseboardController.java` 及同子域 Service/DTO/Entity/Mapper;客户入口按 `merchant:cross-border:pallet-shipping:list/create/edit` 拆分;OpenAPI `src/main/resources/openapi/overseas-business-v1.yaml` |
|
||||
| 海外仓报价、月度汇率、车辆主数据 | 报价 `masterdata/quote/`、汇率 `masterdata/exchange/`;车辆入口 `masterdata/vehicle/adapter/in/web/PlatformVehicleController.java`,应用服务 `application/VehicleApplicationService.java`,持久化端口与适配器 `application/port/out/VehicleRepository.java`、`adapter/out/persistence/`;OpenAPI `src/main/resources/openapi/overseas-master-data-v1.yaml`,车辆档案不含派送履约历史 |
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
头程同单同仓多次实际入库合并为一个计租批次,统一从首次实际入库起算;补入库只追加箱数和计费体积,不重置库龄、免费期或已出库记录。单位体积沿用首次入库比例,后续修改订单体积不改写既有计租比例。其他业务保留各批实际入库时间分别计龄。头程未逐箱清点时,部分出库按来源单据箱数比例估算体积,审核前可调整金额并填写原因。本次不合并历史已存在的计租批次。
|
||||
|
||||
仓储转运库存的正常出库、排车及废弃按本次实际箱长×宽×高÷1,000,000汇总CBM,再乘原入库库龄对应的客户阶梯累计单价,不按原批次箱数平均分摊体积。部分出库必须指明实际箱唛;整托可按该仓位全部可追溯实箱求和。缺少尺寸、箱数不符或无法核对原实收批次时阻断预校验。箱唛与实测体积一并固化在冻结快照,变化后须重新预校验。正常出库同事务保存实箱SHIPPED状态,废弃由原作业保存DISCARDED状态,避免后续重复计租。历史已生成费用保持原快照,不自动重算。本次修改不需要新增数据库字段,尚未发布到测试环境。
|
||||
|
||||
| 规则 | 计费阶梯(USD) | 数量口径 |
|
||||
|---|---|---|
|
||||
| 中转仓租 | 第1–7天免费;8–30天0.8;31–60天1;61–90天1.5;91天起2 | CBM/天,按阶梯累加 |
|
||||
|
||||
+13
-2
@@ -34,13 +34,24 @@ public final class StorageRentDtos {
|
||||
public record Owner(String customerId, String customerName, String businessType, Long businessId,
|
||||
String businessNo, Long orderId, String inboundCode, String warehouseId) { }
|
||||
public record Allocation(long lotId, Long shareId, BigDecimal quantity, BigDecimal amount,
|
||||
LocalDateTime inboundAt, long days, Long previouslyChargedDays) {
|
||||
/** 中转按数量分摊及既有快照不携带退货整托已计费天数。 */
|
||||
LocalDateTime inboundAt, long days, Long previouslyChargedDays,
|
||||
BigDecimal actualVolumeCbm, List<String> cartonMarks) {
|
||||
/** 实测体积与原箱身份一并冻结,消费时核对同一批实物。 */
|
||||
public Allocation { cartonMarks=cartonMarks==null?List.of():List.copyOf(cartonMarks); }
|
||||
/** 原托计费及历史快照不携带转运实测箱体积。 */
|
||||
public Allocation(long lotId, Long shareId, BigDecimal quantity, BigDecimal amount,
|
||||
LocalDateTime inboundAt, long days, Long previouslyChargedDays) {
|
||||
this(lotId,shareId,quantity,amount,inboundAt,days,previouslyChargedDays,null,List.of());
|
||||
}
|
||||
/** 普通来源及既有快照不携带退货整托已计费天数。 */
|
||||
public Allocation(long lotId, Long shareId, BigDecimal quantity, BigDecimal amount,
|
||||
LocalDateTime inboundAt, long days) {
|
||||
this(lotId, shareId, quantity, amount, inboundAt, days, null);
|
||||
}
|
||||
}
|
||||
/** 转运原批次对应的当前实箱测量,体积由服务端按厘米换算。 */
|
||||
public record TransferRentCarton(Long id,String cartonMark,String customerCartonMark,String status,
|
||||
BigDecimal lengthCm,BigDecimal widthCm,BigDecimal heightCm) { }
|
||||
public record PricingTier(String id,Integer startDay,Integer endDay,BigDecimal unitPrice,Integer sortNo,
|
||||
String feeType,String billingUnit) { }
|
||||
public record Pricing(String configId,Long customerId,Integer version,String currency,List<PricingTier> tiers,String source) {
|
||||
|
||||
+36
-1
@@ -3,9 +3,44 @@ package com.baiyue.lastleg.storagerent.mapper;
|
||||
import com.baiyue.lastleg.storagerent.entity.StorageRentBusinessSource;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
/** 同服务本地只读业务归属投影,不借用其他聚合的写 Mapper。 */
|
||||
/** 同服务计租业务归属、实箱测量及随出库原子保存的箱级状态。 */
|
||||
@Mapper
|
||||
public interface StorageRentBusinessSourceMapper {
|
||||
/** 只读取原实收批次、同客户同仓及当前托盘内的转运箱,禁止按箱数猜测体积。 */
|
||||
@Select("""
|
||||
SELECT c.id,c.carton_mark,c.customer_carton_mark,c.status,c.length_cm,c.width_cm,c.height_cm
|
||||
FROM ll_storage_rent_lot l
|
||||
JOIN ll_inbound_stock_receipt r ON l.source_key=CONCAT('INBOUND:',r.id) AND r.customer_id=l.customer_id
|
||||
JOIN ll_warehouse_transfer_order t ON t.id=l.business_id AND t.customer_id=l.customer_id
|
||||
AND t.operating_warehouse_id=l.warehouse_id
|
||||
JOIN ll_inventory_position p ON p.id=#{position} AND p.customer_id=l.customer_id
|
||||
AND p.overseas_warehouse_id=l.warehouse_id
|
||||
JOIN ll_warehouse_transfer_pallet tp ON tp.transfer_order_id=t.id AND tp.customer_id=t.customer_id
|
||||
AND tp.warehouse_pallet_id=r.warehouse_pallet_id AND tp.warehouse_pallet_id=p.pallet_id
|
||||
AND tp.status='COMPLETED'
|
||||
JOIN ll_warehouse_transfer_carton c ON c.transfer_order_id=t.id AND c.customer_id=t.customer_id
|
||||
AND c.transfer_pallet_id=tp.id AND c.status IN ('COUNTED','DISCARD_PENDING')
|
||||
JOIN ll_warehouse_transfer_item i ON i.id=c.transfer_item_id AND i.customer_id=c.customer_id
|
||||
AND i.transfer_order_id=t.id AND i.sku_code=r.sku_code
|
||||
WHERE l.id=#{lot} AND l.customer_id=#{customer} AND l.warehouse_id=#{warehouse}
|
||||
AND l.business_type='WAREHOUSE_TRANSFER' AND l.rule_code='TRANSIT'
|
||||
ORDER BY c.id
|
||||
FOR UPDATE
|
||||
""")
|
||||
java.util.List<com.baiyue.lastleg.storagerent.dto.StorageRentDtos.TransferRentCarton> transferRentCartons(
|
||||
@Param("customer") String customer,@Param("warehouse") String warehouse,
|
||||
@Param("position") Long position,@Param("lot") long lot);
|
||||
/** 普通出库同事务标记已计费实箱,后续部分出库不能重复选择原箱。 */
|
||||
@Update("""
|
||||
<script>
|
||||
UPDATE ll_warehouse_transfer_carton SET status='SHIPPED',updated_at=CURRENT_TIMESTAMP
|
||||
WHERE customer_id=#{customer} AND transfer_order_id=#{transfer} AND status='COUNTED'
|
||||
AND carton_mark IN
|
||||
<foreach collection="marks" item="mark" open="(" separator="," close=")">#{mark}</foreach>
|
||||
</script>
|
||||
""")
|
||||
int shipTransferRentCartons(@Param("customer") String customer,@Param("transfer") Long transfer,
|
||||
@Param("marks") java.util.List<String> marks);
|
||||
/** 换标出库号解析该次独立服务单。 */
|
||||
@Select("SELECT id,relabel_no AS business_no,lastleg_order_id FROM ll_relabel_application WHERE customer_id=#{customer} AND outbound_order_no=#{outbound} LIMIT 1")
|
||||
StorageRentBusinessSource relabel(@Param("customer") String customer,@Param("outbound") String outbound);
|
||||
|
||||
+8
@@ -22,6 +22,14 @@ import java.util.*;
|
||||
/** 仓租全部持久化及同服务来源解析,服务不直接访问 Mapper。 */
|
||||
@Repository
|
||||
public class StorageRentRepository {
|
||||
/** 转运计费只使用本仓位原实收批次可追溯的实箱尺寸。 */
|
||||
public List<TransferRentCarton> transferRentCartons(String customer,String warehouse,Long position,long lot) {
|
||||
return businessSources.transferRentCartons(customer,warehouse,position,lot);
|
||||
}
|
||||
/** 箱级状态与仓租及实际库存共同提交,废弃状态由废弃作业推进。 */
|
||||
public int shipTransferRentCartons(String customer,Long transfer,List<String> marks) {
|
||||
return marks.isEmpty()?0:businessSources.shipTransferRentCartons(customer,transfer,marks);
|
||||
}
|
||||
@Resource private StorageRentLotMapper lots;
|
||||
@Resource private StorageRentShareMapper shares;
|
||||
@Resource private StorageRentPreparationMapper preparations;
|
||||
|
||||
+1
@@ -95,6 +95,7 @@ public class StorageRentReviewService implements StorageRentFeeWorkflow {
|
||||
boolean whole=parts.get(0).previouslyChargedDays()!=null;
|
||||
var quantity=parts.stream().map(Allocation::quantity).reduce(BigDecimal.ZERO,BigDecimal::add);
|
||||
var units=whole?lot.getOriginalUnits().setScale(0,RoundingMode.CEILING)
|
||||
:parts.get(0).actualVolumeCbm()!=null?parts.stream().map(Allocation::actualVolumeCbm).reduce(BigDecimal.ZERO,BigDecimal::add)
|
||||
:lot.getOriginalUnits().multiply(quantity).divide(lot.getOriginalQuantity(),12,RoundingMode.HALF_UP);
|
||||
long previous=whole?parts.get(0).previouslyChargedDays():0;long days=parts.get(0).days();
|
||||
calculations.add(new Calculation(id,whole?"RETURN_PALLET":"RETURN".equals(lot.getRuleCode())?"RETURN_SHARE":"TRANSIT",
|
||||
|
||||
+8
-2
@@ -298,6 +298,10 @@ public class StorageRentService implements WalletResultHandler {
|
||||
for(var allocation:saved.allocations().stream().sorted(Comparator.comparingLong(Allocation::lotId)).toList()){
|
||||
var lot=repository.lockLot(allocation.lotId());
|
||||
if(lot==null||!value.getCustomerId().equals(lot.getCustomerId())||lot.getRemainingQuantity().compareTo(allocation.quantity())<0)throw conflict("原计租批次剩余数量已变化");
|
||||
// 转运按实箱结算,普通出库立即保留箱级离仓事实;废弃仍由原扣减流程改为 DISCARDED。
|
||||
if(allocation.actualVolumeCbm()!=null&&!"DISCARD".equals(request.mode())
|
||||
&&repository.shipTransferRentCartons(value.getCustomerId(),lot.getBusinessId(),allocation.cartonMarks())!=allocation.cartonMarks().size())
|
||||
throw conflict("转运计租实箱已出库或被其他作业占用,请重新预校验");
|
||||
if(allocation.shareId()!=null){var share=repository.lockShare(allocation.shareId());
|
||||
if(share==null||share.getQuantity().compareTo(allocation.quantity())<0)throw conflict("退货计租商品份额已变化");
|
||||
share.setQuantity(share.getQuantity().subtract(allocation.quantity()));repository.save(share);}
|
||||
@@ -447,12 +451,14 @@ public class StorageRentService implements WalletResultHandler {
|
||||
if("SETTLE".equals(operation))value.setSettleEventId(event);else value.setReleaseEventId(event);
|
||||
value.setErrorMessage(null);value.setUpdatedAt(now);repository.save(value);
|
||||
}
|
||||
/** 原托计费期间变化必须重建预占释放旧冻结,避免两次并发出库重复收整托。 */
|
||||
/** 原托期间、转运实际体积或选箱变化必须重建预占,不能沿用其他货物的冻结。 */
|
||||
private static boolean sameBasis(Plan a,Plan b) {
|
||||
if(!Objects.equals(a.owner(),b.owner())||a.allocations().size()!=b.allocations().size())return false;
|
||||
for(int i=0;i<a.allocations().size();i++){var x=a.allocations().get(i);var y=b.allocations().get(i);
|
||||
if(x.lotId()!=y.lotId()||!Objects.equals(x.shareId(),y.shareId())||x.quantity().compareTo(y.quantity())!=0||x.days()!=y.days()
|
||||
||!Objects.equals(x.previouslyChargedDays(),y.previouslyChargedDays()))return false;}
|
||||
||!Objects.equals(x.previouslyChargedDays(),y.previouslyChargedDays())
|
||||
||!Objects.equals(x.cartonMarks(),y.cartonMarks())
|
||||
||(x.actualVolumeCbm()==null?y.actualVolumeCbm()!=null:y.actualVolumeCbm()==null||x.actualVolumeCbm().compareTo(y.actualVolumeCbm())!=0))return false;}
|
||||
return true;
|
||||
}
|
||||
/** 返回前端可执行标记;只有已确认冻结或原已消费请求可继续原扫码幂等恢复。 */
|
||||
|
||||
+58
-4
@@ -31,7 +31,7 @@ public class StorageRentSourceResolver {
|
||||
com.baiyue.lastleg.storagerent.gateway.StorageRentPricingGateway.validate(snapshot,Long.parseLong(plan.owner().customerId()));
|
||||
return reprice(plan,snapshot);
|
||||
}
|
||||
/** 中转按份额计价;退货逐原托只收一次未计费期间,不能按件摊薄或逐箱重复收整托。 */
|
||||
/** 转运按实际出库箱体积计价;退货逐原托只收一次未计费期间。 */
|
||||
private Plan reprice(Plan plan,Pricing pricing) {
|
||||
var allocations=new ArrayList<Allocation>();
|
||||
var chargedLots=new HashSet<Long>();
|
||||
@@ -43,7 +43,12 @@ public class StorageRentSourceResolver {
|
||||
?StorageRentCalculator.returnPalletAmount(pricing,lot.getInboundAt(),plan.calculatedAt(),lot.getOriginalUnits(),previous)
|
||||
:BigDecimal.ZERO;
|
||||
allocations.add(new Allocation(a.lotId(),a.shareId(),a.quantity(),amount,a.inboundAt(),a.days(),previous));
|
||||
} else allocations.add(new Allocation(a.lotId(),a.shareId(),a.quantity(),StorageRentCalculator.amount(pricing,lot.getRuleCode(),lot.getInboundAt(),plan.calculatedAt(),lot.getOriginalUnits(),lot.getOriginalQuantity(),a.quantity()),a.inboundAt(),a.days()));
|
||||
} else {
|
||||
var amount=a.actualVolumeCbm()==null
|
||||
?StorageRentCalculator.amount(pricing,lot.getRuleCode(),lot.getInboundAt(),plan.calculatedAt(),lot.getOriginalUnits(),lot.getOriginalQuantity(),a.quantity())
|
||||
:StorageRentCalculator.unitAmount(pricing,"TRANSIT",a.days()).multiply(a.actualVolumeCbm());
|
||||
allocations.add(new Allocation(a.lotId(),a.shareId(),a.quantity(),amount,a.inboundAt(),a.days(),null,a.actualVolumeCbm(),a.cartonMarks()));
|
||||
}
|
||||
}
|
||||
var calculations=new ArrayList<Calculation>();
|
||||
for(long id:allocations.stream().map(Allocation::lotId).distinct().toList()) {
|
||||
@@ -51,6 +56,7 @@ public class StorageRentSourceResolver {
|
||||
var quantity=parts.stream().map(Allocation::quantity).reduce(BigDecimal.ZERO,BigDecimal::add);
|
||||
boolean returns="RETURN".equals(lot.getRuleCode());long previous=returns?lot.getChargedDays():0;
|
||||
var units=returns?lot.getOriginalUnits().setScale(0,RoundingMode.CEILING)
|
||||
:parts.get(0).actualVolumeCbm()!=null?parts.stream().map(Allocation::actualVolumeCbm).reduce(BigDecimal.ZERO,BigDecimal::add)
|
||||
:lot.getOriginalUnits().multiply(quantity).divide(lot.getOriginalQuantity(),12,RoundingMode.HALF_UP);
|
||||
long days=parts.get(0).days();
|
||||
calculations.add(new Calculation(id,returns?"RETURN_PALLET":"TRANSIT",units,returns?"PALLET":"CBM",days,previous,
|
||||
@@ -125,7 +131,9 @@ public class StorageRentSourceResolver {
|
||||
}
|
||||
var total=shares.stream().map(StorageRentShare::getQuantity).reduce(BigDecimal.ZERO,BigDecimal::add);
|
||||
if(total.compareTo(BigDecimal.valueOf(request.packages()))<0)throw conflict("原实收批次计租数量不足,不能出库");
|
||||
allocations=proportionShares(shares,BigDecimal.valueOf(request.packages()),total,at);
|
||||
var selectedMarks=request.cartonMarks()==null?List.<String>of():request.cartonMarks();
|
||||
if(selectedMarks.isEmpty()&&request.cartonMark()!=null&&!request.cartonMark().isBlank())selectedMarks=List.of(request.cartonMark());
|
||||
allocations=stockAllocations(shares,position,request.packages(),selectedMarks,false,at);
|
||||
if(owner.businessId()==null||owner.businessNo()==null)owner=business(customer,balance.getCustomerName(),"LASTLEG_ORDER",null,balance.getInboundOrderId(),warehouse,balance.getInboundCode());
|
||||
}
|
||||
return new Plan(owner,allocations,at,allocations.stream().map(Allocation::amount).reduce(BigDecimal.ZERO,BigDecimal::add).setScale(2,RoundingMode.HALF_UP));
|
||||
@@ -153,7 +161,13 @@ public class StorageRentSourceResolver {
|
||||
if(shares.isEmpty()){
|
||||
var balance=repository.balance(position.getBalanceId());var order=balance==null?null:repository.order(balance.getInboundOrderId());
|
||||
if(order==null||StorageRentService.eligible(order))throw conflict("废弃原实收批次缺少计租记录");
|
||||
}else if(line.actualPackages()>0){var total=shares.stream().map(StorageRentShare::getQuantity).reduce(BigDecimal.ZERO,BigDecimal::add);if(total.compareTo(BigDecimal.valueOf(line.actualPackages()))<0)throw conflict("废弃原计租批次数量不足");result.addAll(proportionShares(shares,BigDecimal.valueOf(line.actualPackages()),total,at));}
|
||||
}else if(line.actualPackages()>0){
|
||||
var links=discards.cartons(customer,application.getId()).stream().filter(c->item.getId().equals(c.getItemId())).map(c->c.getCartonMark()).toList();
|
||||
var scanned=line.scannedCartonMarks()!=null?line.scannedCartonMarks():request.scannedCartonMarks();
|
||||
// 有申请选箱时只从本行原箱中取实际扫码箱,不能借其他行箱唛扩大计费范围。
|
||||
var selected=scanned==null||scanned.isEmpty()?links:links.isEmpty()?scanned:links.stream().filter(scanned::contains).toList();
|
||||
result.addAll(stockAllocations(shares,position,line.actualPackages(),selected,true,at));
|
||||
}
|
||||
}
|
||||
if(!Objects.equals(pallet==null||pallet.isBlank()?"散箱":pallet.trim(),request.palletNo().trim()))throw conflict("废弃明细不属于本次托盘");
|
||||
}
|
||||
@@ -177,6 +191,46 @@ public class StorageRentSourceResolver {
|
||||
for(int i=0;i<shares.size();i++){var share=shares.get(i);var part=i==shares.size()-1?quantity.subtract(used):quantity.multiply(share.getQuantity()).divide(total,12,RoundingMode.DOWN);used=used.add(part);if(part.signum()>0)result.add(allocation(repository.lockLot(share.getLotId()),share.getId(),part,at));}
|
||||
return result;
|
||||
}
|
||||
/** 转运按实际箱尺寸计费,数量仅用于扣减原库存;整托也必须有完整箱级测量。 */
|
||||
private List<Allocation> stockAllocations(List<StorageRentShare> shares,
|
||||
com.baiyue.lastleg.inventory.entity.InventoryPosition position,long packages,List<String> marks,
|
||||
boolean discard,LocalDateTime at) {
|
||||
var total=shares.stream().map(StorageRentShare::getQuantity).reduce(BigDecimal.ZERO,BigDecimal::add);
|
||||
if(total.compareTo(BigDecimal.valueOf(packages))<0)throw conflict("原实收批次计租数量不足,不能出库");
|
||||
var transfer=shares.stream().filter(s->{var lot=repository.lockLot(s.getLotId());return lot!=null&&"WAREHOUSE_TRANSFER".equals(lot.getBusinessType())&&"TRANSIT".equals(lot.getRuleCode());}).toList();
|
||||
if(transfer.isEmpty())return proportionShares(shares,BigDecimal.valueOf(packages),total,at);
|
||||
if(transfer.size()!=shares.size())throw conflict("转运计租来源混合,需按实际箱核对原批次");
|
||||
var selected=new HashSet<String>();
|
||||
for(String mark:marks)if(mark==null||mark.isBlank()||!selected.add(mark.trim()))throw conflict("转运计租箱唛不能为空或重复");
|
||||
var result=new ArrayList<Allocation>();var matched=new HashSet<String>();var physical=new HashSet<Long>();long count=0;
|
||||
for(var share:transfer){
|
||||
var lot=repository.lockLot(share.getLotId());
|
||||
var boxes=repository.transferRentCartons(position.getCustomerId(),position.getOverseasWarehouseId(),position.getId(),lot.getId());
|
||||
var chosen=boxes.stream().filter(c->discard?"DISCARD_PENDING".equals(c.status()):"COUNTED".equals(c.status()))
|
||||
.filter(c->selected.isEmpty()||selected.contains(c.cartonMark())||selected.contains(c.customerCartonMark())).toList();
|
||||
if(chosen.isEmpty())continue;
|
||||
BigDecimal volume=BigDecimal.ZERO;var canonical=new ArrayList<String>();
|
||||
for(var box:chosen){
|
||||
if(!physical.add(box.id())||box.cartonMark()==null)throw conflict("转运箱的原计租批次不唯一");
|
||||
if(box.lengthCm()==null||box.widthCm()==null||box.heightCm()==null
|
||||
||box.lengthCm().signum()<=0||box.widthCm().signum()<=0||box.heightCm().signum()<=0)throw conflict("转运箱缺少实际长宽高,不能计算仓租");
|
||||
volume=volume.add(box.lengthCm().multiply(box.widthCm()).multiply(box.heightCm()).movePointLeft(6));
|
||||
canonical.add(box.cartonMark());
|
||||
if(selected.contains(box.cartonMark()))matched.add(box.cartonMark());
|
||||
if(selected.contains(box.customerCartonMark()))matched.add(box.customerCartonMark());
|
||||
}
|
||||
var quantity=BigDecimal.valueOf(chosen.size());
|
||||
if(quantity.compareTo(share.getQuantity())>0)throw conflict("转运实箱与原计租剩余数量不一致");
|
||||
canonical.sort(String::compareTo);long days=StorageRentCalculator.days(lot.getInboundAt(),at);
|
||||
result.add(new Allocation(lot.getId(),share.getId(),quantity,
|
||||
StorageRentCalculator.unitAmount("TRANSIT",days).multiply(volume),lot.getInboundAt(),days,null,volume,canonical));
|
||||
count+=chosen.size();
|
||||
}
|
||||
// 无逐箱扫码仅允许本仓位全部实箱一次出库,部分出库不得挑选平均体积或猜箱。
|
||||
if(count!=packages||!matched.equals(selected)||!selected.isEmpty()&&selected.size()!=packages)
|
||||
throw conflict("转运仓租需提交本次实际出库箱唛,数量或原批次尺寸记录不完整");
|
||||
return result;
|
||||
}
|
||||
/** 商品份额仅追溯原托及扣减库存;金额在完整扫码汇总后按原托去重计算。 */
|
||||
private List<Allocation> returnAllocations(String customer,String warehouse,String mark,String sku,Long quantity,LocalDateTime at) {
|
||||
var carton=repository.carton(mark);if(carton==null||!customer.equals(carton.getCustomerId())||!warehouse.equals(carton.getOverseasWarehouseId()))throw conflict("原退货箱不属于当前客户和仓库");
|
||||
|
||||
+25
@@ -82,6 +82,31 @@ class StorageRentServiceTest {
|
||||
var fee=new OverseasFeeSource();fee.setSourceFeeId(701L);when(fees.publishSettled(anyString(),anyString(),any(),any(),any())).thenReturn(fee);
|
||||
}
|
||||
|
||||
/** 同数量同金额也必须核对原箱身份,实测体积变化不能继续沿用冻结。 */
|
||||
@ParameterizedTest @ValueSource(booleans={false,true})
|
||||
void transferVolumeOrCartonChangeInvalidatesFreeze(boolean changeVolume) {
|
||||
var a=plan.allocations().get(0);
|
||||
plan=new Plan(plan.owner(),List.of(new Allocation(a.lotId(),a.shareId(),a.quantity(),a.amount(),a.inboundAt(),a.days(),null,new BigDecimal("0.036"),List.of("LARGE"))),plan.calculatedAt(),plan.amount());
|
||||
ready();
|
||||
plan=new Plan(plan.owner(),List.of(new Allocation(a.lotId(),a.shareId(),a.quantity(),a.amount(),a.inboundAt(),a.days(),null,
|
||||
new BigDecimal(changeVolume?"0.035":"0.036"),List.of(changeVolume?"LARGE":"OTHER"))),plan.calculatedAt(),plan.amount());
|
||||
assertThatThrownBy(()->service.consume("9",request)).hasMessageContaining("计费时间或货物已变化");
|
||||
assertThat(lot.getRemainingQuantity()).isEqualByComparingTo("10");
|
||||
verifyNoInteractions(fees);
|
||||
}
|
||||
|
||||
/** 成功消费实箱后同事务保存离仓状态,后续扫码不能再次使用这些箱计费。 */
|
||||
@Test void transferConsumptionPersistsCartonIdentityWithOriginalVolume() {
|
||||
var a=plan.allocations().get(0);lot.setBusinessId(81L);
|
||||
plan=new Plan(plan.owner(),List.of(new Allocation(a.lotId(),a.shareId(),a.quantity(),a.amount(),a.inboundAt(),a.days(),null,
|
||||
new BigDecimal("0.036"),List.of("LARGE"))),plan.calculatedAt(),plan.amount());
|
||||
when(repository.shipTransferRentCartons("101",81L,List.of("LARGE"))).thenReturn(1);
|
||||
ready();service.consume("9",request);
|
||||
verify(repository).shipTransferRentCartons("101",81L,List.of("LARGE"));
|
||||
assertThat(current.getPlanJson()).contains("0.036").contains("LARGE");
|
||||
assertThat(lot.getRemainingQuantity()).isEqualByComparingTo("6");
|
||||
}
|
||||
|
||||
/** 未收到成功冻结回执时,原库存与计租份额都不能扣减。 */
|
||||
@Test void pendingOrFailedFreezeCannotConsumeInventory() {
|
||||
assertThat(service.prepare("9",request).ready()).isFalse();
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.baiyue.lastleg.storagerent.service;
|
||||
|
||||
import com.baiyue.lastleg.storagerent.mapper.StorageRentBusinessSourceMapper;
|
||||
import com.baiyue.lastleg.storagerent.repository.StorageRentRepository;
|
||||
import org.apache.ibatis.mapping.Environment;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
|
||||
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
/** 执行实际Mapper验证实收批次、客户、仓位、原托隔离及实箱离仓更新。 */
|
||||
class StorageRentTransferVolumePersistenceTest {
|
||||
/** 大小箱测量按原批次读取,其他仓位和客户不得混入,出库后不再返回原箱。 */
|
||||
@Test void readsMeasuredCartonsFromExactReceiptAndUpdatesOnlyOwnedCarton() {
|
||||
var ds=new DriverManagerDataSource("jdbc:h2:mem:rent-volume-"+UUID.randomUUID()+";MODE=MySQL;DB_CLOSE_DELAY=-1","sa","");
|
||||
var db=new JdbcTemplate(ds);
|
||||
db.execute("CREATE TABLE ll_storage_rent_lot(id BIGINT,source_key VARCHAR(100),customer_id VARCHAR(30),warehouse_id VARCHAR(30),business_id BIGINT,business_type VARCHAR(40),rule_code VARCHAR(20))");
|
||||
db.execute("CREATE TABLE ll_inbound_stock_receipt(id BIGINT,customer_id VARCHAR(30),warehouse_pallet_id BIGINT,sku_code VARCHAR(30))");
|
||||
db.execute("CREATE TABLE ll_warehouse_transfer_order(id BIGINT,customer_id VARCHAR(30),operating_warehouse_id VARCHAR(30))");
|
||||
db.execute("CREATE TABLE ll_inventory_position(id BIGINT,customer_id VARCHAR(30),overseas_warehouse_id VARCHAR(30),pallet_id BIGINT)");
|
||||
db.execute("CREATE TABLE ll_warehouse_transfer_pallet(id BIGINT,transfer_order_id BIGINT,customer_id VARCHAR(30),warehouse_pallet_id BIGINT,status VARCHAR(30))");
|
||||
db.execute("CREATE TABLE ll_warehouse_transfer_item(id BIGINT,transfer_order_id BIGINT,customer_id VARCHAR(30),sku_code VARCHAR(30))");
|
||||
db.execute("CREATE TABLE ll_warehouse_transfer_carton(id BIGINT,transfer_order_id BIGINT,customer_id VARCHAR(30),transfer_pallet_id BIGINT,transfer_item_id BIGINT,carton_mark VARCHAR(30),customer_carton_mark VARCHAR(30),status VARCHAR(30),length_cm DECIMAL(12,2),width_cm DECIMAL(12,2),height_cm DECIMAL(12,2),updated_at TIMESTAMP)");
|
||||
db.update("INSERT INTO ll_storage_rent_lot VALUES(51,'INBOUND:101','A','WH',81,'WAREHOUSE_TRANSFER','TRANSIT')");
|
||||
db.update("INSERT INTO ll_inbound_stock_receipt VALUES(101,'A',31,'SKU')");
|
||||
db.update("INSERT INTO ll_warehouse_transfer_order VALUES(81,'A','WH')");
|
||||
db.update("INSERT INTO ll_inventory_position VALUES(21,'A','WH',31),(22,'A','WH',32),(23,'B','WH',31)");
|
||||
db.update("INSERT INTO ll_warehouse_transfer_pallet VALUES(71,81,'A',31,'COMPLETED'),(72,81,'A',32,'COMPLETED')");
|
||||
db.update("INSERT INTO ll_warehouse_transfer_item VALUES(91,81,'A','SKU'),(92,81,'A','OTHER')");
|
||||
db.update("INSERT INTO ll_warehouse_transfer_carton VALUES(1,81,'A',71,91,'SMALL',NULL,'COUNTED',30,20,20,NULL),(2,81,'A',71,91,'LARGE',NULL,'DISCARD_PENDING',40,30,30,NULL),(3,81,'B',71,91,'FOREIGN',NULL,'COUNTED',40,30,30,NULL),(4,81,'A',72,91,'OTHER-PALLET',NULL,'COUNTED',40,30,30,NULL),(5,81,'A',71,92,'OTHER-SKU',NULL,'COUNTED',40,30,30,NULL)");
|
||||
var config=new Configuration(new Environment("test",new JdbcTransactionFactory(),ds));
|
||||
config.setMapUnderscoreToCamelCase(true);config.addMapper(StorageRentBusinessSourceMapper.class);
|
||||
try(var session=new SqlSessionFactoryBuilder().build(config).openSession()) {
|
||||
var repository=new StorageRentRepository();ReflectionTestUtils.setField(repository,"businessSources",session.getMapper(StorageRentBusinessSourceMapper.class));
|
||||
var boxes=repository.transferRentCartons("A","WH",21L,51L);
|
||||
assertThat(boxes).extracting(b->b.cartonMark()).containsExactly("SMALL","LARGE");
|
||||
assertThat(boxes.get(1).lengthCm()).isEqualByComparingTo("40");
|
||||
assertThat(repository.transferRentCartons("B","WH",21L,51L)).isEmpty();
|
||||
assertThat(repository.transferRentCartons("A","OTHER",21L,51L)).isEmpty();
|
||||
assertThat(repository.transferRentCartons("A","WH",22L,51L)).isEmpty();
|
||||
assertThat(repository.transferRentCartons("A","WH",23L,51L)).isEmpty();
|
||||
assertThat(repository.shipTransferRentCartons("B",81L,List.of("SMALL"))).isZero();
|
||||
assertThat(repository.shipTransferRentCartons("A",81L,List.of("SMALL","LARGE"))).isEqualTo(1);
|
||||
assertThat(repository.transferRentCartons("A","WH",21L,51L)).extracting(b->b.cartonMark()).containsExactly("LARGE");
|
||||
assertThat(repository.shipTransferRentCartons("A",81L,List.of("SMALL"))).isZero();
|
||||
}
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package com.baiyue.lastleg.storagerent.service;
|
||||
|
||||
import com.baiyue.lastleg.storagerent.dto.StorageRentDtos.*;
|
||||
import com.baiyue.lastleg.storagerent.entity.*;
|
||||
import com.baiyue.lastleg.storagerent.repository.StorageRentRepository;
|
||||
import com.baiyue.lastleg.storagerent.gateway.StorageRentPricingGateway;
|
||||
import com.baiyue.lastleg.discard.repository.CargoDiscardPdaRepository;
|
||||
import com.baiyue.lastleg.discard.entity.*;
|
||||
import com.baiyue.lastleg.discard.pda.dto.PdaCargoDiscardDtos.ActualLine;
|
||||
import com.baiyue.lastleg.dispatch.entity.*;
|
||||
import com.baiyue.lastleg.delivery.entity.*;
|
||||
import com.baiyue.lastleg.inventory.entity.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/** 大小箱混托必须按实际出库箱体积计租,数量不再充当体积比例。 */
|
||||
class StorageRentTransferVolumeTest {
|
||||
private final StorageRentRepository repository=mock(StorageRentRepository.class);
|
||||
private final CargoDiscardPdaRepository discards=mock(CargoDiscardPdaRepository.class);
|
||||
private final StorageRentPricingGateway pricing=mock(StorageRentPricingGateway.class);
|
||||
private final StorageRentSourceResolver resolver=new StorageRentSourceResolver();
|
||||
private final LocalDateTime at=LocalDateTime.of(2026,11,4,7,37);
|
||||
private final TransferRentCarton small=box(1L,"SMALL","COUNTED","30","20","20");
|
||||
private final TransferRentCarton large=box(2L,"LARGE","COUNTED","40","30","30");
|
||||
|
||||
/** 原批次两箱共0.048立方米,库龄96天,模拟本次真实单据。 */
|
||||
@BeforeEach void setup() {
|
||||
ReflectionTestUtils.setField(resolver,"repository",repository);
|
||||
ReflectionTestUtils.setField(resolver,"discards",discards);
|
||||
ReflectionTestUtils.setField(resolver,"pricingGateway",pricing);
|
||||
when(pricing.current("101")).thenReturn(StorageRentCalculator.defaults(101L));
|
||||
var lot=new StorageRentLot();lot.setId(51L);lot.setBusinessType("WAREHOUSE_TRANSFER");lot.setRuleCode("TRANSIT");
|
||||
lot.setOriginalQuantity(new BigDecimal("2"));lot.setRemainingQuantity(new BigDecimal("2"));
|
||||
lot.setOriginalUnits(new BigDecimal("0.048"));lot.setInboundAt(at.minusDays(96));
|
||||
var share=new StorageRentShare();share.setId(61L);share.setLotId(51L);share.setQuantity(new BigDecimal("2"));
|
||||
var position=new InventoryPosition();position.setId(21L);position.setCustomerId("101");position.setOverseasWarehouseId("9");
|
||||
position.setBalanceId(71L);position.setBatchNo("ZY-1");position.setPalletNo("PALLET");
|
||||
var balance=new InventoryBalance();balance.setCustomerId("101");balance.setInboundOrderId(91L);
|
||||
var transfer=new StorageRentBusinessSource();transfer.setId(81L);transfer.setBusinessNo("ZY-1");transfer.setInboundOrderId(91L);
|
||||
var dispatch=new DispatchAggregate();dispatch.setId(1L);dispatch.setStatus("ACTIVE");dispatch.setStage("LOADING");dispatch.setOverseasWarehouseId("9");
|
||||
var item=new DispatchItem();item.setDispatchOrderId(1L);item.setCustomerId("101");item.setOverseasWarehouseId("9");
|
||||
item.setSourceBusinessType("WAREHOUSE_TRANSFER");item.setSourceBusinessNo("ZY-1");
|
||||
when(repository.lockLot(51L)).thenReturn(lot);when(repository.stockShares("101","9",71L,"ZY-1")).thenReturn(List.of(share));
|
||||
when(repository.position(21L)).thenReturn(position);when(repository.balance(71L)).thenReturn(balance);
|
||||
when(repository.transfer("101","ZY-1")).thenReturn(transfer);
|
||||
when(repository.dispatch("PC-1")).thenReturn(dispatch);when(repository.dispatchItem(11L)).thenReturn(item);
|
||||
when(repository.transferRentCartons("101","9",21L,51L)).thenReturn(List.of(small,large));
|
||||
}
|
||||
|
||||
/** 大箱0.036立方米应收3.79美元,小箱0.012立方米应收1.26美元。 */
|
||||
@Test void dispatchUsesSelectedCartonVolumeAndKeepsSavedPricing() {
|
||||
var plan=resolver.resolve("9",request(1,List.of("LARGE")),at);
|
||||
assertThat(plan.amount()).isEqualByComparingTo("3.79");
|
||||
assertThat(plan.calculations().get(0).billingQuantity()).isEqualByComparingTo("0.036");
|
||||
assertThat(plan.allocations().get(0).quantity()).isEqualByComparingTo("1");
|
||||
assertThat(plan.allocations().get(0).cartonMarks()).containsExactly("LARGE");
|
||||
clearInvocations(pricing);
|
||||
assertThat(resolver.resolve("9",request(1,List.of("LARGE")),at,plan.pricing()).amount()).isEqualByComparingTo("3.79");
|
||||
verifyNoInteractions(pricing);
|
||||
assertThat(resolver.resolve("9",request(1,List.of("SMALL")),at).amount()).isEqualByComparingTo("1.26");
|
||||
}
|
||||
|
||||
/** 整托按所有实箱体积求和,未指定箱唛的部分出库必须阻断。 */
|
||||
@Test void wholePalletSumsRealVolumesButPartialPickNeedsIdentity() {
|
||||
var plan=resolver.resolve("9",request(2,List.of()),at);
|
||||
assertThat(plan.amount()).isEqualByComparingTo("5.06");
|
||||
assertThat(plan.calculations().get(0).billingQuantity()).isEqualByComparingTo("0.048");
|
||||
assertThatThrownBy(()->resolver.resolve("9",request(1,List.of()),at)).hasMessageContaining("实际出库箱唛");
|
||||
assertThatThrownBy(()->resolver.resolve("9",request(1,List.of("OTHER")),at)).hasMessageContaining("实际出库箱唛");
|
||||
assertThatThrownBy(()->resolver.resolve("9",request(2,List.of("LARGE","LARGE")),at)).hasMessageContaining("重复");
|
||||
}
|
||||
|
||||
/** 直接出库与排车使用同一真实计费体积。 */
|
||||
@Test void outboundUsesActualVolume() {
|
||||
var task=new OutboundPdaTask();task.setCustomerId("101");task.setOutboundOrderId(41L);task.setStatus("PENDING");
|
||||
var outbound=new OutboundOrder();outbound.setOverseasWarehouseId("9");outbound.setOutboundType("WAREHOUSE_TRANSFER_OUTBOUND");
|
||||
var item=new OutboundPalletItem();item.setCustomerId("101");item.setOutboundOrderId(41L);item.setOrderRefs("ZY-1");
|
||||
when(repository.task("9","TASK")).thenReturn(task);when(repository.outbound(41L)).thenReturn(outbound);when(repository.outboundItem(11L)).thenReturn(item);
|
||||
var request=new PrepareRequest("OUTBOUND","TASK",11L,21L,1L,null,null,"scan",List.of("LARGE"),null,null);
|
||||
assertThat(resolver.resolve("9",request,at).amount()).isEqualByComparingTo("3.79");
|
||||
}
|
||||
|
||||
/** 废弃选中的大箱仍按0.036立方米计费,预留状态不使它被平均分摊。 */
|
||||
@Test void discardBillsOnlyTheReservedActualCarton() {
|
||||
var application=new CargoDiscardApplication();application.setId(101L);application.setCustomerId("101");application.setDiscardNo("CD-1");application.setStatus("APPROVED");
|
||||
var item=new CargoDiscardItem();item.setId(201L);item.setApplicationId(101L);item.setCustomerId("101");item.setDiscardPackages(1);item.setInventoryPositionId(21L);
|
||||
var link=new CargoDiscardCarton();link.setItemId(201L);link.setCartonMark("LARGE");
|
||||
when(discards.lockApplication("CD-1","9")).thenReturn(application);when(discards.item(201L)).thenReturn(item);when(discards.cartons("101",101L)).thenReturn(List.of(link));
|
||||
when(repository.transferRentCartons("101","9",21L,51L)).thenReturn(List.of(small,box(2L,"LARGE","DISCARD_PENDING","40","30","30")));
|
||||
var request=new PrepareRequest("DISCARD","CD-1",null,null,null,null,"PALLET","scan",null,null,null,
|
||||
List.of(new ActualLine(201L,1)),List.of(),List.of("LARGE"),false);
|
||||
var plan=resolver.resolve("9",request,at);
|
||||
assertThat(plan.amount()).isEqualByComparingTo("3.79");
|
||||
assertThat(plan.calculations().get(0).billingQuantity()).isEqualByComparingTo("0.036");
|
||||
assertThat(plan.owner().businessNo()).isEqualTo("CD-1");
|
||||
}
|
||||
|
||||
/** 缺测量、错误来源或已被废弃预留的箱不允许降级成平均体积。 */
|
||||
@Test void missingMeasurementsAndUnavailableCartonsBlockCharging() {
|
||||
when(repository.transferRentCartons("101","9",21L,51L)).thenReturn(List.of(box(2L,"LARGE","COUNTED","0","30","30")));
|
||||
assertThatThrownBy(()->resolver.resolve("9",request(1,List.of("LARGE")),at)).hasMessageContaining("实际长宽高");
|
||||
when(repository.transferRentCartons("101","9",21L,51L)).thenReturn(List.of());
|
||||
assertThatThrownBy(()->resolver.resolve("9",request(1,List.of("LARGE")),at)).hasMessageContaining("实际出库箱唛");
|
||||
when(repository.transferRentCartons("101","9",21L,51L)).thenReturn(List.of(box(2L,"LARGE","DISCARD_PENDING","40","30","30")));
|
||||
assertThatThrownBy(()->resolver.resolve("9",request(1,List.of("LARGE")),at)).hasMessageContaining("实际出库箱唛");
|
||||
}
|
||||
|
||||
/** 创建真实排车扫码请求,费用客户和仓库仅由持久化单据给出。 */
|
||||
private PrepareRequest request(long packages,List<String> marks) {
|
||||
return new PrepareRequest("DISPATCH","PC-1",11L,21L,packages,null,null,"scan",marks,null,null);
|
||||
}
|
||||
/** 尺寸保持厘米原精度,不预先将单箱体积舍入到三位。 */
|
||||
private TransferRentCarton box(Long id,String mark,String status,String length,String width,String height) {
|
||||
return new TransferRentCarton(id,mark,null,status,new BigDecimal(length),new BigDecimal(width),new BigDecimal(height));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user