@@ -3,13 +3,18 @@ package com.baiyue.order.service;
import com.baiyue.common.core.api.ResultCode ;
import com.baiyue.common.core.exception.BizException ;
import com.baiyue.order.client.OrderBatchFileClient ;
import com.baiyue.order.client.MerchantPricingCatalogClient ;
import com.baiyue.order.client.MerchantWarehouseCatalogClient ;
import com.baiyue.order.dto.CustomerAddressDtos ;
import com.baiyue.order.dto.MerchantBatchDraftDtos ;
import com.baiyue.order.dto.MerchantBatchImportDtos ;
import com.baiyue.order.dto.OrderRequests ;
import com.fasterxml.jackson.databind.ObjectMapper ;
import jakarta.annotation.Resource ;
import org.apache.poi.ss.usermodel.Cell ;
import org.apache.poi.ss.usermodel.DataValidationConstraint ;
import org.apache.poi.ss.usermodel.Row ;
import org.apache.poi.ss.util.CellRangeAddressList ;
import org.apache.poi.xssf.usermodel.XSSFWorkbook ;
import org.springframework.beans.factory.annotation.Value ;
import org.springframework.stereotype.Service ;
@@ -21,7 +26,10 @@ import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets ;
import java.security.MessageDigest ;
import java.util.ArrayList ;
import java.util.LinkedHashMap ;
import java.util.List ;
import java.util.Map ;
import java.util.function.Function ;
@Service
public class MerchantBatchImportService {
@@ -29,35 +37,91 @@ public class MerchantBatchImportService {
private static final long MAX_BYTES = 5L * 1024 * 1024 ;
@Resource private MerchantBatchDraftService drafts ;
@Resource private OrderBatchFileClient files ;
@Resource private CustomerAddressService addresses ;
@Resource private MerchantWarehouseCatalogClient warehouses ;
@Resource private MerchantPricingCatalogClient pricing ;
@Resource private ObjectMapper json ;
@Value ( " ${baiyue.security.internal-service-token:} " ) private String token ;
private static final List < String > TEMPLATE_HEADERS = List . of (
" clientRowId " , " customerName " , " transportMode " , " tradeTerm " , " destinationCountryCode " ,
" destinationWarehouseId " , " destinationWarehouseName " , " expectedShipDate " , " customerRemark " ,
" pickupMethod " , " requestedPickupAt " , " senderAddressId " , " receivingWarehouseId " ,
" receivingWarehouseName " , " channelCode " , " channelName " , " taxMode " , " pricingTaxMode " ,
" containerMode " , " customsInspection " , " destinationType " , " privateDestinationAddressId " ,
" amazonWarehouseCode " , " shipmentId " , " referenceId " , " fulfillmentType " , " cargo.cargoName " ,
" cargo.packageType " , " cargo.packageCount " , " cargo.grossWeightKg " , " cargo.volumeCbm " ,
" cargo.material " , " cargo.woodenProduct " , " cargo.skuNo " , " cargo.upcNo " , " cargo.shipmentId " ,
" cargo.referenceId " ) ;
private static final List < Column > COLUMNS = List . of (
new Column ( " 客户行号 " , " clientRowId " , null ) , new Column ( " 客户名称 " , " customerName " , null ) ,
new Column ( " 运输方式 " , " transportMode " , " transport " ) , new Column ( " 贸易术语 " , " tradeTerm " , null ) ,
new Column ( " 目的国家代码 " , " destinationCountryCode " , null ) , new Column ( " 目的仓库 " , " $destinationWarehouse " , " destinationWarehouses " ) ,
new Column ( " 预计出运日期 " , " expectedShipDate " , null ) , new Column ( " 客户备注 " , " customerRemark " , null ) ,
new Column ( " 提货方式 " , " pickupMethod " , " pickup " ) , new Column ( " 预约提货时间 " , " requestedPickupAt " , null ) ,
new Column ( " 取货地址 " , " $senderAddress " , " pickupAddresses " ) , new Column ( " 国内收货仓 " , " $receivingWarehouse " , " receivingWarehouses " ) ,
new Column ( " 运输渠道 " , " $channel " , " channels " ) , new Column ( " 报关方式 " , " taxMode " , " tax " ) ,
new Column ( " 计价税模式 " , " pricingTaxMode " , " pricingTax " ) , new Column ( " 装箱方式 " , " containerMode " , " container " ) ,
new Column ( " 是否需要商检 " , " customsInspection " , " boolean " ) , new Column ( " 目的类型 " , " destinationType " , " destination " ) ,
new Column ( " 私人目的仓地址 " , " $privateAddress " , " privateAddresses " ) , new Column ( " 亚马逊仓库代码 " , " amazonWarehouseCode " , null ) ,
new Column ( " 货件编号 " , " shipmentId " , null ) , new Column ( " 参考编号 " , " referenceId " , null ) ,
new Column ( " 履约类型 " , " fulfillmentType " , " fulfillment " ) , new Column ( " 货物名称 " , " cargo.cargoName " , null ) ,
new Column ( " 包装类型 " , " cargo.packageType " , " package " ) , new Column ( " 包装数量 " , " cargo.packageCount " , null ) ,
new Column ( " 毛重(千克) " , " cargo.grossWeightKg " , null ) , new Column ( " 体积(立方米) " , " cargo.volumeCbm " , null ) ,
new Column ( " 材质 " , " cargo.material " , null ) , new Column ( " 是否木制品 " , " cargo.woodenProduct " , " boolean " ) ,
new Column ( " 是否危险品 " , " cargo.dangerousGoods " , " boolean " ) , new Column ( " SKU " , " cargo.skuNo " , null ) ,
new Column ( " UPC " , " cargo.upcNo " , null ) , new Column ( " 货物货件编号 " , " cargo.shipmentId " , null ) ,
new Column ( " 货物参考编号 " , " cargo.referenceId " , null ) ) ;
private static final Map < String , List < String > > STATIC_OPTIONS = Map . of (
" transport " , List . of ( " 海运 " , " 空运 " , " 铁路 " , " 卡车 " ) ,
" pickup " , List . of ( " 自行快递 " , " 平台上门提货 " , " 自行送仓 " ) ,
" tax " , List . of ( " 买单报关 " , " 退税报关 " ) , " pricingTax " , List . of ( " 包税 " , " 不包税 " ) ,
" container " , List . of ( " 整柜 " , " 散货拼箱 " ) , " boolean " , List . of ( " 是 " , " 否 " ) ,
" destination " , List . of ( " 亚马逊FBA " , " 海外仓 " , " 私人地址 " ) ,
" fulfillment " , List . of ( " 普通头程 " , " 一件代发 " ) , " package " , List . of ( " 纸箱 " , " 托盘 " , " 木箱 " ) ) ;
private static final Map < String , String > VALUE_CODES = Map . ofEntries (
Map . entry ( " 海运 " , " SEA " ) , Map . entry ( " 空运 " , " AIR " ) , Map . entry ( " 铁路 " , " RAIL " ) , Map . entry ( " 卡车 " , " TRUCK " ) ,
Map . entry ( " 自行快递 " , " SELF_EXPRESS " ) , Map . entry ( " 平台上门提货 " , " PICKUP " ) , Map . entry ( " 自行送仓 " , " SELF_TRUCK " ) ,
Map . entry ( " 买单报关 " , " BUY_DECLARATION " ) , Map . entry ( " 退税报关 " , " TAX_REFUND " ) ,
Map . entry ( " 包税 " , " TAX_INCLUDED " ) , Map . entry ( " 不包税 " , " TAX_EXCLUDED " ) ,
Map . entry ( " 整柜 " , " FULL_CONTAINER " ) , Map . entry ( " 散货拼箱 " , " LCL " ) , Map . entry ( " 是 " , " true " ) , Map . entry ( " 否 " , " false " ) ,
Map . entry ( " 亚马逊FBA " , " FBA " ) , Map . entry ( " 海外仓 " , " OVERSEAS " ) , Map . entry ( " 私人地址 " , " PRIVATE " ) ,
Map . entry ( " 普通头程 " , " STANDARD " ) , Map . entry ( " 一件代发 " , " ONE_CLICK_DROPSHIP " ) ,
Map . entry ( " 纸箱 " , " CARTON " ) , Map . entry ( " 托盘 " , " PALLET " ) , Map . entry ( " 木箱 " , " WOODEN_CASE " ) ) ;
public byte [ ] template ( ) {
public byte [ ] template ( long tenantId , String authorization ) {
Catalog catalog = catalog ( tenantId , authorization ) ;
try ( var workbook = new XSSFWorkbook ( ) ; var out = new ByteArrayOutputStream ( ) ) {
var sheet = workbook . createSheet ( " 订单导入 " ) ;
Row header = sheet . createRow ( 0 ) ;
for ( int i = 0 ; i < TEMPLATE_HEADERS . size ( ) ; i + + ) header . createCell ( i ) . setCellValue ( TEMPLATE_HEADERS . get ( i ) ) ;
Row example = sheet . createRow ( 1 ) ;
example . createCell ( 0 ) . setCellValue ( " row-001 " ) ;
example . createCell ( 1 ) . setCellValue ( " 示例客户 " ) ; example . createCell ( 2 ) . setCellValue ( " SEA " ) ;
example . createCell ( 9 ) . setCellValue ( " SELF_EXPRESS " ) ; example . createCell ( 12 ) . setCellValue ( " 190000000000000101 " ) ;
example . createCell ( 13 ) . setCellValue ( " 国内收货仓 " ) ; example . createCell ( 14 ) . setCellValue ( " CHANNEL-001 " ) ;
example . createCell ( 15 ) . setCellValue ( " 示例渠道 " ) ; example . createCell ( 16 ) . setCellValue ( " BUY_DECLARATION " ) ;
example . createCell ( 17 ) . setCellValue ( " TAX_EXCLUDED " ) ; example . createCell ( 20 ) . setCellValue ( " OVERSEAS " ) ;
example . createCell ( 26 ) . setCellValue ( " 示例货物 " ) ; example . createCell ( 27 ) . setCellValue ( " CARTON " ) ;
example . createCell ( 28 ) . setCellValue ( " 1 " ) ; example . createCell ( 29 ) . setCellValue ( " 1.000 " ) ;
example . createCell ( 30 ) . setCellValue ( " 0.0100 " ) ; example . createCell ( 32 ) . setCellValue ( " false " ) ;
for ( int i = 0 ; i < TEMPLATE_HEADERS . size ( ) ; i + + ) sheet . setColumnWidth ( i , 20 * 256 ) ;
for ( int i = 0 ; i < COLUMNS . size ( ) ; i + + ) header . createCell ( i ) . setCellValue ( COLUMNS . get ( i ) . title ( ) ) ;
sheet . createFreezePane ( 0 , 1 ) ;
for ( int i = 0 ; i < COLUMNS . size ( ) ; i + + ) sheet . setColumnWidth ( i , 20 * 256 ) ;
var options = workbook . createSheet ( " 选项数据 " ) ;
Map < String , List < String > > optionValues = new LinkedHashMap < > ( STATIC_OPTIONS ) ;
optionValues . put ( " pickupAddresses " , catalog . pickupAddresses ( ) . keySet ( ) . stream ( ) . toList ( ) ) ;
optionValues . put ( " privateAddresses " , catalog . privateAddresses ( ) . keySet ( ) . stream ( ) . toList ( ) ) ;
optionValues . put ( " receivingWarehouses " , catalog . receivingWarehouses ( ) . keySet ( ) . stream ( ) . toList ( ) ) ;
optionValues . put ( " destinationWarehouses " , catalog . destinationWarehouses ( ) . keySet ( ) . stream ( ) . toList ( ) ) ;
optionValues . put ( " channels " , catalog . channels ( ) . keySet ( ) . stream ( ) . toList ( ) ) ;
int optionCol = 0 ;
Map < String , String > ranges = new LinkedHashMap < > ( ) ;
for ( var entry : optionValues . entrySet ( ) ) {
if ( entry . getValue ( ) . isEmpty ( ) ) continue ;
for ( int row = 0 ; row < entry . getValue ( ) . size ( ) ; row + + ) {
var target = options . getRow ( row ) = = null ? options . createRow ( row ) : options . getRow ( row ) ;
target . createCell ( optionCol ) . setCellValue ( entry . getValue ( ) . get ( row ) ) ;
}
String rangeName = " option_ " + optionCol ;
var name = workbook . createName ( ) ; name . setNameName ( rangeName ) ;
name . setRefersToFormula ( " '选项数据'!$ " + columnLetter ( optionCol ) + " $1:$ " + columnLetter ( optionCol ) + " $ " + entry . getValue ( ) . size ( ) ) ;
ranges . put ( entry . getKey ( ) , rangeName ) ; optionCol + + ;
}
var helper = sheet . getDataValidationHelper ( ) ;
for ( int col = 0 ; col < COLUMNS . size ( ) ; col + + ) {
String range = ranges . get ( COLUMNS . get ( col ) . optionKey ( ) ) ;
if ( range = = null ) continue ;
DataValidationConstraint constraint = helper . createFormulaListConstraint ( range ) ;
var validation = helper . createValidation ( constraint , new CellRangeAddressList ( 1 , MAX_ROWS , col , col ) ) ;
validation . setShowErrorBox ( true ) ; validation . createErrorBox ( " 选项不正确 " , " 请从下拉列表中选择 " ) ;
sheet . addValidationData ( validation ) ;
}
workbook . setSheetHidden ( workbook . getSheetIndex ( options ) , true ) ;
var guide = workbook . createSheet ( " 填写说明 " ) ;
String [ ] guides = { " 每一行代表一张订单,最多填写50行。 " , " 客户行号由您自定义且在文件内唯一,用于定位错误行。 " ,
" 带下拉的字段必须从列表选择;系统导入时会重新核验仓库、渠道和地址的状态及客户归属。 " ,
" 日期格式:yyyy-MM-dd;预约提货时间格式:yyyy-MM-dd HH:mm:ss。 " , " 本说明页不会被导入,请勿在订单导入页放置示例行。 " } ;
for ( int i = 0 ; i < guides . length ; i + + ) guide . createRow ( i ) . createCell ( 0 ) . setCellValue ( guides [ i ] ) ;
guide . setColumnWidth ( 0 , 100 * 256 ) ;
workbook . write ( out ) ;
return out . toByteArray ( ) ;
} catch ( Exception exception ) {
@@ -65,7 +129,7 @@ public class MerchantBatchImportService {
}
}
public MerchantBatchImportDtos . Response importFile ( long tenantId , long userId , String username ,
public MerchantBatchImportDtos . Response importFile ( long tenantId , long userId , String username , String authorization ,
String idempotencyKey , MerchantBatchImportDtos . Mode mode ,
MultipartFile file ) {
requireFile ( file ) ;
@@ -73,7 +137,7 @@ public class MerchantBatchImportService {
byte [ ] content ;
try { content = file . getBytes ( ) ; }
catch ( Exception exception ) { throw new BizException ( ResultCode . BAD_REQUEST , " 无法读取 Excel 文件 " ) ; }
List < MerchantBatchDraftDtos . Item > items = parse ( content ) ;
List < MerchantBatchDraftDtos . Item > items = parse ( tenantId , authorization , content ) ;
Long sourceFileId = upload ( tenantId , userId , batchNo , " ORDER_BATCH_IMPORT_SOURCE " ,
new BytesMultipartFile ( " file " , safeName ( file . getOriginalFilename ( ) ) ,
" application/vnd.openxmlformats-officedocument.spreadsheetml.sheet " , content ) ) ;
@@ -85,17 +149,19 @@ public class MerchantBatchImportService {
return new MerchantBatchImportDtos . Response ( batchNo , mode , sourceFileId , errorFileId , result ) ;
}
private List < MerchantBatchDraftDtos . Item > parse ( byte [ ] content ) {
List < MerchantBatchDraftDtos . Item > parse ( long tenantId , String authorization , byte [ ] content ) {
Catalog catalog = catalog ( tenantId , authorization ) ;
try ( var workbook = new XSSFWorkbook ( new ByteArrayInputStream ( content ) ) ) {
var sheet = workbook . getSheetAt ( 0 ) ;
if ( sheet . getLastRowNum ( ) < 1 ) throw new BizException ( ResultCode . VALIDATION_FAILED , " Excel 没有订单数据行 " ) ;
List < MerchantBatchDraftDtos . Item > items = new ArrayList < > ( ) ;
Row header = sheet . getRow ( 0 ) ;
if ( header = = null | | ! " clientRowId " . equals ( text ( header . getCell ( 0 ) ) ) ) {
if ( header = = null | | ! COLUMNS . get ( 0 ) . title ( ) . equals ( text ( header . getCell ( 0 ) ) ) ) {
throw new BizException ( ResultCode . VALIDATION_FAILED , " Excel 模板表头不正确 " ) ;
}
List < String > headers = new ArrayList < > ( ) ;
for ( int col = 0 ; col < header . getLastCellNum ( ) ; col + + ) headers . add ( text ( header . getCell ( col ) ) ) ;
if ( header . getLastCellNum ( ) ! = COLUMNS . size ( ) ) throw new BizException ( ResultCode . VALIDATION_FAILED , " Excel 模板表头不正确 " ) ;
for ( int col = 0 ; col < COLUMNS . size ( ) ; col + + ) if ( ! COLUMNS . get ( col ) . title ( ) . equals ( text ( header . getCell ( col ) ) ) )
throw new BizException ( ResultCode . VALIDATION_FAILED , " Excel 模板表头不正确 " ) ;
for ( int rowNo = 1 ; rowNo < = sheet . getLastRowNum ( ) ; rowNo + + ) {
Row row = sheet . getRow ( rowNo ) ;
if ( row = = null | | ! StringUtils . hasText ( text ( row . getCell ( 0 ) ) ) ) continue ;
@@ -104,15 +170,19 @@ public class MerchantBatchImportService {
try {
var draft = json . createObjectNode ( ) ;
var cargo = json . createObjectNode ( ) ;
for ( int col = 1 ; col < headers . size ( ) ; col + + ) {
String field = headers . get ( col ) ; String value = text ( row . getCell ( col ) ) ;
for ( int col = 1 ; col < COLUMNS . size ( ) ; col + + ) {
String field = COLUMNS . get ( col ) . field ( ) ; String value = text ( row . getCell ( col ) ) ;
if ( ! StringUtils . hasText ( field ) | | ! StringUtils . hasText ( value ) ) continue ;
value = VALUE_CODES . getOrDefault ( value , value ) ;
if ( field . startsWith ( " $ " ) ) { applySelection ( draft , field , value , catalog ) ; continue ; }
if ( field . startsWith ( " cargo. " ) ) cargo . put ( field . substring ( " cargo. " . length ( ) ) , value ) ;
else draft . put ( field , value ) ;
}
draft . set ( " cargoes " , json . createArrayNode ( ) . add ( cargo ) ) ;
items . add ( new MerchantBatchDraftDtos . Item ( rowId ,
json . treeToValue ( draft , OrderRequests . SaveRequest . class ) ) ) ;
} catch ( BizException exception ) {
items . add ( new MerchantBatchDraftDtos . Item ( rowId , null , exception . getMessage ( ) ) ) ;
} catch ( Exception exception ) {
items . add ( new MerchantBatchDraftDtos . Item ( rowId , null , " Excel 行字段格式不正确 " ) ) ;
}
@@ -127,7 +197,7 @@ public class MerchantBatchImportService {
try ( var workbook = new XSSFWorkbook ( ) ; var out = new ByteArrayOutputStream ( ) ) {
var sheet = workbook . createSheet ( " 错误报告 " ) ;
Row header = sheet . createRow ( 0 ) ;
String [ ] headers = { " rowIndex " , " clientRowId " , " errorCode " , " errorMessage " } ;
String [ ] headers = { " Excel行号 " , " 客户行号 " , " 错误代码 " , " 错误说明 " } ;
for ( int i = 0 ; i < headers . length ; i + + ) header . createCell ( i ) . setCellValue ( headers [ i ] ) ;
int rowNo = 1 ;
for ( var item : result . items ( ) ) if ( ! item . success ( ) ) {
@@ -143,6 +213,101 @@ public class MerchantBatchImportService {
} catch ( Exception exception ) { throw new BizException ( ResultCode . INTERNAL_ERROR , " 错误报告生成失败 " ) ; }
}
private Catalog catalog ( long tenantId , String authorization ) {
if ( ! StringUtils . hasText ( authorization ) ) throw new BizException ( ResultCode . UNAUTHORIZED , " 登录凭证缺失 " ) ;
try {
var warehouseResponse = warehouses . page ( authorization , 1 , 200 , " ACTIVE " ) ;
if ( warehouseResponse = = null | | ! warehouseResponse . successful ( ) | | warehouseResponse . data ( ) = = null ) {
throw new BizException ( ResultCode . SERVICE_UNAVAILABLE , " 仓库选项暂不可用 " ) ;
}
List < MerchantWarehouseCatalogClient . Warehouse > allWarehouses = warehouseResponse . data ( ) . records ( ) ;
if ( warehouseResponse . data ( ) . pages ( ) > 1 ) {
throw new BizException ( ResultCode . SERVICE_UNAVAILABLE , " 可用仓库超过模板支持范围,请联系平台处理 " ) ;
}
var receiving = optionMap ( allWarehouses . stream ( ) . filter ( value - > " DOMESTIC " . equals ( value . warehouseType ( ) ) ) . toList ( ) ,
value - > label ( value . warehouseName ( ) , value . warehouseCode ( ) ) ) ;
if ( receiving . isEmpty ( ) ) throw new BizException ( ResultCode . SERVICE_UNAVAILABLE , " 暂无可用国内收货仓 " ) ;
var destination = optionMap ( allWarehouses . stream ( ) . filter ( value - > ! " DOMESTIC " . equals ( value . warehouseType ( ) ) ) . toList ( ) ,
value - > label ( value . warehouseName ( ) , value . warehouseCode ( ) ) ) ;
Map < String , MerchantPricingCatalogClient . Channel > channels = new LinkedHashMap < > ( ) ;
for ( var warehouse : receiving . values ( ) ) {
var response = pricing . channels ( authorization , warehouse . id ( ) ) ;
if ( response = = null | | ! response . successful ( ) | | response . data ( ) = = null ) {
throw new BizException ( ResultCode . SERVICE_UNAVAILABLE , " 渠道选项暂不可用 " ) ;
}
for ( var channel : response . data ( ) ) {
String display = label ( channel . channelName ( ) , channel . channelCode ( ) ) ;
var previous = channels . putIfAbsent ( display , channel ) ;
if ( previous ! = null & & ! previous . channelCode ( ) . equals ( channel . channelCode ( ) ) ) {
throw new BizException ( ResultCode . CONFLICT , " 渠道显示名称重复,请联系平台处理 " ) ;
}
}
}
if ( channels . isEmpty ( ) ) throw new BizException ( ResultCode . SERVICE_UNAVAILABLE , " 暂无可用运输渠道 " ) ;
return new Catalog (
addressOptions ( addresses . list ( tenantId , " PICKUP " ) ) ,
addressOptions ( addresses . list ( tenantId , " PRIVATE_DESTINATION " ) ) ,
receiving , destination , channels ) ;
} catch ( BizException exception ) {
throw exception ;
} catch ( RuntimeException exception ) {
throw new BizException ( ResultCode . SERVICE_UNAVAILABLE , " 订单模板选项暂不可用 " ) ;
}
}
private static Map < String , CustomerAddressDtos . Response > addressOptions ( List < CustomerAddressDtos . Response > values ) {
return optionMap ( values , value - > String . join ( " | " , value . addressName ( ) , value . contactName ( ) , value . city ( ) , value . addressLine ( ) ) ) ;
}
private static < T > Map < String , T > optionMap ( List < T > values , Function < T , String > labeler ) {
Map < String , T > result = new LinkedHashMap < > ( ) ;
for ( T value : values ) {
String label = labeler . apply ( value ) ;
if ( result . putIfAbsent ( label , value ) ! = null ) {
throw new BizException ( ResultCode . CONFLICT , " 模板选项显示名称重复,请联系平台处理 " ) ;
}
}
return result ;
}
private static void applySelection ( com . fasterxml . jackson . databind . node . ObjectNode draft , String field ,
String value , Catalog catalog ) {
switch ( field ) {
case " $senderAddress " - > draft . put ( " senderAddressId " , required ( catalog . pickupAddresses ( ) , value , " 取货地址 " ) . id ( ) ) ;
case " $privateAddress " - > draft . put ( " privateDestinationAddressId " , required ( catalog . privateAddresses ( ) , value , " 私人目的仓地址 " ) . id ( ) ) ;
case " $receivingWarehouse " - > {
var warehouse = required ( catalog . receivingWarehouses ( ) , value , " 国内收货仓 " ) ;
draft . put ( " receivingWarehouseId " , warehouse . id ( ) ) ; draft . put ( " receivingWarehouseName " , warehouse . warehouseName ( ) ) ;
}
case " $destinationWarehouse " - > {
var warehouse = required ( catalog . destinationWarehouses ( ) , value , " 目的仓库 " ) ;
draft . put ( " destinationWarehouseId " , warehouse . id ( ) ) ; draft . put ( " destinationWarehouseName " , warehouse . warehouseName ( ) ) ;
}
case " $channel " - > {
var channel = required ( catalog . channels ( ) , value , " 运输渠道 " ) ;
draft . put ( " channelCode " , channel . channelCode ( ) ) ; draft . put ( " channelName " , channel . channelName ( ) ) ;
}
default - > throw new BizException ( ResultCode . VALIDATION_FAILED , " Excel 模板字段不正确 " ) ;
}
}
private static < T > T required ( Map < String , T > options , String value , String name ) {
T selected = options . get ( value ) ;
if ( selected = = null ) throw new BizException ( ResultCode . VALIDATION_FAILED , name + " 不存在、已停用或不属于当前客户 " ) ;
return selected ;
}
private static String label ( String name , String code ) { return name + " | " + code ; }
private static String columnLetter ( int column ) { return String . valueOf ( ( char ) ( 'A' + column ) ) ; }
private record Column ( String title , String field , String optionKey ) { }
private record Catalog (
Map < String , CustomerAddressDtos . Response > pickupAddresses ,
Map < String , CustomerAddressDtos . Response > privateAddresses ,
Map < String , MerchantWarehouseCatalogClient . Warehouse > receivingWarehouses ,
Map < String , MerchantWarehouseCatalogClient . Warehouse > destinationWarehouses ,
Map < String , MerchantPricingCatalogClient . Channel > channels ) { }
private Long upload ( long tenantId , long userId , String batchNo , String type , MultipartFile file ) {
try {
var response = files . upload ( token , userId , tenantId , type , batchNo , file ) ;