统一修改ruoyi->bms
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bms.price.domain.BmsProduct;
|
||||
import com.bms.price.domain.BmsProductCountry;
|
||||
|
||||
/**
|
||||
* 产品列表 服务层
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
public interface IBmsProductService
|
||||
{
|
||||
public BmsProduct selectBmsProductById(Long productId);
|
||||
|
||||
public List<BmsProduct> selectBmsProductList(BmsProduct product);
|
||||
|
||||
public int insertBmsProduct(BmsProduct product);
|
||||
|
||||
public int updateBmsProduct(BmsProduct product);
|
||||
|
||||
/**
|
||||
* 修改产品国家明细行(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期),状态按日期重算
|
||||
*/
|
||||
public int updateBmsProductCountry(BmsProductCountry country);
|
||||
|
||||
public int deleteBmsProductByIds(Long[] productIds);
|
||||
|
||||
public List<BmsProductCountry> selectCountryListByProductCode(String productCode);
|
||||
|
||||
public List<BmsProductCountry> selectCountryListByProductCode(String productCode, Date effectiveDate);
|
||||
|
||||
public List<BmsProductCountry> selectCountryListByProductCode(String productCode, Date effectiveDate, Long productId);
|
||||
|
||||
/**
|
||||
* 导入威狮B2C小包价表:产品目录 + 各产品 sheet 国家×重量段(含运费/挂号费/尺寸/尾程/时效)
|
||||
*
|
||||
* @param file 上传的 xlsx/xls
|
||||
* @param updateSupport unused; same code+date never overwrites, different date always inserts
|
||||
* @param operName 操作人
|
||||
* @return 导入结果说明(可含 HTML 换行)
|
||||
*/
|
||||
public String importProductExcel(MultipartFile file, boolean updateSupport, String operName) throws Exception;
|
||||
|
||||
/**
|
||||
* Import a single-country cost sheet (catalog + product weight rows).
|
||||
* The destination country is chosen by the user, not read from the sheet.
|
||||
*
|
||||
* @param file uploaded xlsx/xls
|
||||
* @param countryCode ISO country code (bms_region.country_code_2)
|
||||
* @param operName operator
|
||||
* @return HTML result message
|
||||
*/
|
||||
public String importCountryProductExcel(MultipartFile file, String countryCode, String operName) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.price.domain.PrcBillingTask;
|
||||
import com.bms.price.domain.vo.BillingTaskSubmitRequest;
|
||||
|
||||
public interface IPrcBillingTaskService
|
||||
{
|
||||
/** Warehouse inbound: create task + publish MQ. */
|
||||
PrcBillingTask submitInbound(BillingTaskSubmitRequest request);
|
||||
|
||||
/** MQ consumer entry: Oracle query -> bill -> writeback success status. */
|
||||
void processTask(Long taskId);
|
||||
|
||||
PrcBillingTask getById(Long taskId);
|
||||
|
||||
/** Admin list (failed / waiting / filter). */
|
||||
List<PrcBillingTask> selectList(PrcBillingTask query);
|
||||
|
||||
/**
|
||||
* Manual resync: reset to WAITING then process again.
|
||||
* @return task after retry
|
||||
*/
|
||||
PrcBillingTask retryTask(Long taskId);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import com.bms.price.domain.PrcOrderBill;
|
||||
import com.bms.price.domain.vo.AddIncomeBillFeeRequest;
|
||||
import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest;
|
||||
import com.bms.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.bms.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.bms.price.domain.vo.TmsOrderBillStatusResult;
|
||||
|
||||
/** 订单计费:落库 prc_order_bill + prc_income_bill/+fee + prc_pay_bill/+fee。 */
|
||||
public interface IPrcOrderBillService
|
||||
{
|
||||
TmsPriceQuoteResult bill(TmsPriceBillRequest request);
|
||||
|
||||
/**
|
||||
* 退费:保留原费用明细,在 prc_income_bill_fee 增加一条负金额(feeType=REFUND),重算应收并回补余额。
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
*/
|
||||
TmsPriceQuoteResult refund(String orderNo);
|
||||
|
||||
List<PrcOrderBill> selectBillList(PrcOrderBill query);
|
||||
|
||||
/** 订单号下拉搜索选项(模糊匹配,去重限量) */
|
||||
List<String> listOrderNoOptions(String keyword);
|
||||
|
||||
/** 运单号下拉搜索选项(模糊匹配,去重限量) */
|
||||
List<String> listTrackingNoOptions(String keyword);
|
||||
|
||||
/** 转单号下拉搜索选项(模糊匹配,去重限量) */
|
||||
List<String> listTransferNoOptions(String keyword);
|
||||
|
||||
TmsPriceQuoteResult getBill(String billId);
|
||||
|
||||
TmsPriceQuoteResult getBillByOrderNo(String orderNo);
|
||||
|
||||
/** Manual add income fee from surcharge config; refresh receivable totals. */
|
||||
TmsPriceQuoteResult addIncomeFee(AddIncomeBillFeeRequest request);
|
||||
|
||||
/** Update manual UNBILLED income fee; refresh receivable totals. */
|
||||
TmsPriceQuoteResult updateIncomeFee(UpdateIncomeBillFeeRequest request);
|
||||
|
||||
/** Delete manual UNBILLED income fee; refresh receivable totals. */
|
||||
TmsPriceQuoteResult deleteIncomeFee(String feeId);
|
||||
|
||||
/** 提供给 TMS 判断订单是否可以继续处理的客户状态查询。 */
|
||||
TmsOrderBillStatusResult queryTmsOrderBillStatus(String identifier);
|
||||
|
||||
/**
|
||||
* 余额充值等审核入账后调用(异步):按客户代码查询欠费(ARREARS)订单,
|
||||
* 按下单时间升序依次更为 NORMAL;更新日志写入 ES(logType=ARREARS)。
|
||||
*
|
||||
* @param customerCode 客户代码
|
||||
* @param amount 入账金额(依次抵扣订单 income_total,通常为本位币CNY)
|
||||
*/
|
||||
void clearCreditArrearsByAmount(String customerCode, BigDecimal amount);
|
||||
|
||||
/**
|
||||
* 计费列表手动更新欠费状态:仅 ARREARS 可更为 NORMAL。
|
||||
* 校验逻辑与计费同步额度一致(客户正常 + 有足够可用额度),不通过则拒绝。
|
||||
*
|
||||
* @param billId 订单计费ID
|
||||
* @return 更新后的计费结果
|
||||
*/
|
||||
TmsPriceQuoteResult updateCreditArrearsStatus(String billId);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.bms.price.domain.PrcCustomerPriceSet;
|
||||
import com.bms.price.domain.PrcPriceSet;
|
||||
import com.bms.price.domain.PrcPriceSetCheckItem;
|
||||
import com.bms.price.domain.PrcPriceSetDetail;
|
||||
import com.bms.price.domain.PrcPriceSetSaveTask;
|
||||
|
||||
/**
|
||||
* 价格套餐 服务层
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
public interface IPrcPriceSetService
|
||||
{
|
||||
public PrcPriceSet selectPrcPriceSetById(Long priceSetId);
|
||||
|
||||
public List<PrcPriceSet> selectPrcPriceSetList(PrcPriceSet set);
|
||||
|
||||
public Long savePrcPriceSet(PrcPriceSet set);
|
||||
|
||||
public int updatePrcPriceSet(PrcPriceSet set);
|
||||
|
||||
public int deletePrcPriceSetByIds(Long[] priceSetIds);
|
||||
|
||||
public int submit(Long priceSetId, String updateBy);
|
||||
|
||||
public int approve(Long priceSetId, String updateBy);
|
||||
|
||||
public int reject(Long priceSetId, String updateBy);
|
||||
|
||||
public int disable(Long priceSetId, String updateBy);
|
||||
|
||||
/** Re-enable a stopped package (S -> E). */
|
||||
public int enable(Long priceSetId, String updateBy);
|
||||
|
||||
/**
|
||||
* Activate a draft or reschedule a pending version with user-specified effectiveTime.
|
||||
* effectiveTime <= now -> EFFECT; future -> PENDING(3). Then refresh 0/3/1/2.
|
||||
*/
|
||||
public int activateVersion(Long psvId, Date effectiveTime, String updateBy);
|
||||
|
||||
/**
|
||||
* Copy source version into a new draft version under the same package:
|
||||
* insert prc_price_set_version + copy prc_price_set_detail (+ surcharge snapshots).
|
||||
* @return new psvId
|
||||
*/
|
||||
public Long copyVersion(Long sourcePsvId, String createBy);
|
||||
|
||||
/** Delete a draft version and its details / surcharge snapshots */
|
||||
public int deleteVersion(Long psvId, String updateBy);
|
||||
|
||||
public Long copy(Long priceSetId, String createBy);
|
||||
|
||||
public List<PrcPriceSetCheckItem> validate(PrcPriceSet set);
|
||||
|
||||
public List<PrcPriceSetDetail> selectDetailList(PrcPriceSetDetail detail);
|
||||
|
||||
/**
|
||||
* 版本明细搜索下拉选项:按版本去重后的 产品/国家/分区 列表。
|
||||
* 返回 { products: [{code,name}], countries: [{code,name}], zones: [{code,name}] }
|
||||
*/
|
||||
public Map<String, Object> selectDetailOptions(Long psvId);
|
||||
|
||||
public int insertDetail(PrcPriceSetDetail detail);
|
||||
|
||||
public int updateDetail(PrcPriceSetDetail detail);
|
||||
|
||||
public int deleteDetailByIds(Long[] psdIds);
|
||||
|
||||
public List<PrcCustomerPriceSet> selectCustomerList(PrcCustomerPriceSet customer);
|
||||
|
||||
public int insertCustomer(PrcCustomerPriceSet customer);
|
||||
|
||||
public int updateCustomer(PrcCustomerPriceSet customer);
|
||||
|
||||
public int deleteCustomerByIds(Long[] customerPricesetIds);
|
||||
|
||||
/** chain expire_time = next version effective_time, then refresh 0/3/1/2 */
|
||||
public int refreshAllVersionStatus();
|
||||
|
||||
/** submit async save, return taskId immediately */
|
||||
public String submitSaveAsync(PrcPriceSet set);
|
||||
|
||||
/**
|
||||
* Submit async batch write of detail surcharge snapshots (product-level pick can hit many rows).
|
||||
* @return taskId for polling getSaveTask
|
||||
*/
|
||||
public String submitDetailSurchargeAsync(List<PrcPriceSetDetail> details);
|
||||
|
||||
/** Sync batch update of detail surcharge snapshots */
|
||||
public int batchUpdateDetailSurcharges(List<PrcPriceSetDetail> details);
|
||||
|
||||
/** poll async save progress */
|
||||
public PrcPriceSetSaveTask getSaveTask(String taskId);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.price.domain.PrcSurchargeRel;
|
||||
|
||||
/**
|
||||
* 附加费与产品/明细关联 服务层
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
public interface IPrcSurchargeRelService
|
||||
{
|
||||
/** 查询某产品已关联的附加费(含附加费与明细展示字段) */
|
||||
public List<PrcSurchargeRel> selectRelByProductCode(String productCode);
|
||||
|
||||
public List<PrcSurchargeRel> selectPrcSurchargeRelList(PrcSurchargeRel rel);
|
||||
|
||||
public int insertPrcSurchargeRel(PrcSurchargeRel rel);
|
||||
|
||||
public int deletePrcSurchargeRelByIds(Long[] relIds);
|
||||
|
||||
/** 删除产品时清理其关联 */
|
||||
public int deleteRelByProductCodes(String[] productCodes);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.price.domain.PrcSurcharge;
|
||||
|
||||
/**
|
||||
* 附加费配置 服务层
|
||||
*
|
||||
* 附加费为计费主数据:编辑=原记录作废+插入新记录(版本化),删除=仅作废不物理删除。
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
public interface IPrcSurchargeService
|
||||
{
|
||||
public PrcSurcharge selectPrcSurchargeById(Long scId);
|
||||
|
||||
public List<PrcSurcharge> selectPrcSurchargeList(PrcSurcharge surcharge);
|
||||
|
||||
public int insertPrcSurcharge(PrcSurcharge surcharge);
|
||||
|
||||
/** 版本化更新:原记录作废(ss_code=vd),插入新记录,产品关联重指向新记录;状态继承原记录 */
|
||||
public int updatePrcSurcharge(PrcSurcharge surcharge);
|
||||
|
||||
/** 停用(原地修改 ss_code=of,不新增数据) */
|
||||
public int disablePrcSurchargeByIds(Long[] scIds, String operator);
|
||||
|
||||
/** 启用(原地修改 ss_code=on,不新增数据) */
|
||||
public int enablePrcSurchargeByIds(Long[] scIds, String operator);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.price.domain.PrcWeightSectionDetail;
|
||||
import com.bms.price.domain.PrcWeightSectionTemplate;
|
||||
|
||||
/**
|
||||
* 重量段管理 服务层
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
public interface IPrcWeightService
|
||||
{
|
||||
public PrcWeightSectionTemplate selectPrcWeightSectionTemplateById(Long wstId);
|
||||
|
||||
public List<PrcWeightSectionTemplate> selectPrcWeightSectionTemplateList(PrcWeightSectionTemplate template);
|
||||
|
||||
public int insertPrcWeightSectionTemplate(PrcWeightSectionTemplate template);
|
||||
|
||||
public int updatePrcWeightSectionTemplate(PrcWeightSectionTemplate template);
|
||||
|
||||
public int deletePrcWeightSectionTemplateByIds(Long[] wstIds);
|
||||
|
||||
public PrcWeightSectionDetail selectPrcWeightSectionDetailById(Long wsdId);
|
||||
|
||||
public List<PrcWeightSectionDetail> selectPrcWeightSectionDetailList(PrcWeightSectionDetail detail);
|
||||
|
||||
public int insertPrcWeightSectionDetail(PrcWeightSectionDetail detail);
|
||||
|
||||
public int updatePrcWeightSectionDetail(PrcWeightSectionDetail detail);
|
||||
|
||||
public int deletePrcWeightSectionDetailByIds(Long[] wsdIds);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bms.price.domain.PrcZone;
|
||||
import com.bms.price.domain.PrcZoneDetail;
|
||||
import com.bms.price.domain.PrcZoneRegion;
|
||||
|
||||
public interface IPrcZoneService
|
||||
{
|
||||
public PrcZoneRegion selectZoneRegionById(Long regionId);
|
||||
|
||||
public List<PrcZoneRegion> selectZoneRegionList(PrcZoneRegion query);
|
||||
|
||||
/** Delete all zones (and details) for the given bms_region ids */
|
||||
public int deleteZonesByRegionIds(Long[] regionIds);
|
||||
|
||||
public PrcZone selectPrcZoneById(Long zoneId);
|
||||
|
||||
public List<PrcZone> selectPrcZoneList(PrcZone zone);
|
||||
|
||||
public int insertPrcZone(PrcZone zone);
|
||||
|
||||
public int updatePrcZone(PrcZone zone);
|
||||
|
||||
public int deletePrcZoneByIds(Long[] zoneIds);
|
||||
|
||||
public PrcZoneDetail selectPrcZoneDetailById(Long detailId);
|
||||
|
||||
public List<PrcZoneDetail> selectPrcZoneDetailList(PrcZoneDetail detail);
|
||||
|
||||
public int insertPrcZoneDetail(PrcZoneDetail detail);
|
||||
|
||||
public int updatePrcZoneDetail(PrcZoneDetail detail);
|
||||
|
||||
public int deletePrcZoneDetailByIds(Long[] detailIds);
|
||||
|
||||
public String importZoneExcel(MultipartFile file, String operName) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import com.bms.price.domain.PrcQuote;
|
||||
import com.bms.price.domain.vo.TmsPriceQuoteRequest;
|
||||
import com.bms.price.domain.vo.TmsPriceQuoteResult;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TMS freight quote / bill calc service (shared by console trial and upstream bill).
|
||||
*
|
||||
* Match order:
|
||||
* 1) Customer binds (not stopped), bind_priority ASC
|
||||
* 2) Active package version at bill-time end-of-day (effective_time / expire_time)
|
||||
* 3) FIXED detail override, else DISCOUNT on that version
|
||||
* 4) Else bms_product_country default
|
||||
* Package hit uses version surcharge snapshot when present; else product library.
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
public interface ITmsPriceQuoteService
|
||||
{
|
||||
TmsPriceQuoteResult quote(TmsPriceQuoteRequest request);
|
||||
|
||||
/** Calc only, no prc_quote persist. Used by receivable billing. */
|
||||
TmsPriceQuoteResult calculate(TmsPriceQuoteRequest request);
|
||||
|
||||
List<PrcQuote> selectQuoteList(PrcQuote query);
|
||||
|
||||
TmsPriceQuoteResult getQuote(String quoteId);
|
||||
}
|
||||
+1785
File diff suppressed because it is too large
Load Diff
+410
@@ -0,0 +1,410 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.config.BillingProperties;
|
||||
import com.bms.price.domain.PrcBillingTask;
|
||||
import com.bms.price.domain.PrcOrderBill;
|
||||
import com.bms.price.domain.vo.BillingTaskSubmitRequest;
|
||||
import com.bms.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.bms.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.bms.price.enums.BillTaskSuccessStatus;
|
||||
import com.bms.price.enums.BillTaskType;
|
||||
import com.bms.price.mapper.PrcBillingTaskMapper;
|
||||
import com.bms.price.mapper.PrcOrderBillMapper;
|
||||
import com.bms.price.mq.BillingTaskMessage;
|
||||
import com.bms.price.mq.IBillingTaskMqPublisher;
|
||||
import com.bms.price.oracle.OracleOrderBillMapper;
|
||||
import com.bms.price.oracle.OracleUpstreamOrderService;
|
||||
import com.bms.price.service.IPrcBillingTaskService;
|
||||
import com.bms.price.service.IPrcOrderBillService;
|
||||
|
||||
@Service
|
||||
public class PrcBillingTaskServiceImpl implements IPrcBillingTaskService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(PrcBillingTaskServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private PrcBillingTaskMapper billingTaskMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcOrderBillMapper orderBillMapper;
|
||||
|
||||
@Autowired
|
||||
private IBillingTaskMqPublisher billingTaskMqPublisher;
|
||||
|
||||
@Autowired
|
||||
private OracleUpstreamOrderService oracleUpstreamOrderService;
|
||||
|
||||
@Autowired
|
||||
private OracleOrderBillMapper oracleOrderBillMapper;
|
||||
|
||||
@Autowired
|
||||
private IPrcOrderBillService prcOrderBillService;
|
||||
|
||||
@Autowired
|
||||
private BillingProperties billingProperties;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PrcBillingTask submitInbound(BillingTaskSubmitRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ServiceException("billing task request required");
|
||||
}
|
||||
String transferNo = trim(request.getTransferNo());
|
||||
String trackingNo = trim(request.getTrackingNo());
|
||||
String orderNo = trim(request.getOrderNo());
|
||||
Date billTime = request.getBillTime();
|
||||
if (StringUtils.isEmpty(transferNo) && StringUtils.isEmpty(trackingNo) && StringUtils.isEmpty(orderNo))
|
||||
{
|
||||
throw new ServiceException("transferNo or trackingNo or orderNo required");
|
||||
}
|
||||
if (billTime == null)
|
||||
{
|
||||
throw new ServiceException("billTime required (billing time from mobile)");
|
||||
}
|
||||
|
||||
BillTaskType type = BillTaskType.fromCode(request.getBillType());
|
||||
if (type == null)
|
||||
{
|
||||
type = resolveAutoType(transferNo, trackingNo, orderNo);
|
||||
}
|
||||
|
||||
PrcBillingTask task = new PrcBillingTask();
|
||||
task.setTransferNo(transferNo);
|
||||
task.setTrackingNo(trackingNo);
|
||||
task.setOrderNo(orderNo);
|
||||
task.setBillTime(billTime);
|
||||
task.setBillType(type.getCode());
|
||||
task.setSuccessStatus(BillTaskSuccessStatus.WAITING.getCode());
|
||||
task.setCreateBy("tms");
|
||||
billingTaskMapper.insertPrcBillingTask(task);
|
||||
|
||||
billingTaskMqPublisher.publish(new BillingTaskMessage(task.getTaskId()));
|
||||
log.info("billing task submitted taskId={} type={} billTime={} transferNo={} trackingNo={} orderNo={}",
|
||||
task.getTaskId(), task.getBillType(), billTime, transferNo, trackingNo, orderNo);
|
||||
return task;
|
||||
}
|
||||
|
||||
private static final int BILL_DB_RETRY_MAX = 3;
|
||||
private static final long BILL_DB_RETRY_BASE_MS = 80L;
|
||||
|
||||
@Override
|
||||
public void processTask(Long taskId)
|
||||
{
|
||||
if (taskId == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
PrcBillingTask task = billingTaskMapper.selectPrcBillingTaskById(taskId);
|
||||
if (task == null)
|
||||
{
|
||||
log.warn("billing task not found taskId={}", taskId);
|
||||
return;
|
||||
}
|
||||
if (BillTaskSuccessStatus.SUCCESS.getCode().equals(task.getSuccessStatus()))
|
||||
{
|
||||
log.info("billing task already success taskId={}", taskId);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (task.getBillTime() == null)
|
||||
{
|
||||
throw new ServiceException("task billTime empty (billing time from mobile required)");
|
||||
}
|
||||
BillTaskType type = BillTaskType.fromCode(task.getBillType());
|
||||
if (BillTaskType.REFUND == type)
|
||||
{
|
||||
String refundOrderNo = resolveRefundOrderNo(task);
|
||||
log.info("billing task REFUND path taskId={} orderNo={}", taskId, refundOrderNo);
|
||||
TmsPriceQuoteResult result = refundWithDbRetry(taskId, refundOrderNo);
|
||||
writeback(task, BillTaskSuccessStatus.SUCCESS, result == null ? null : result.getBillId(),
|
||||
null);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> row = oracleUpstreamOrderService.findMainOrder(
|
||||
task.getTransferNo(), task.getOrderNo(), task.getTrackingNo());
|
||||
if (row == null || row.isEmpty())
|
||||
{
|
||||
throw new ServiceException("Oracle main order not found");
|
||||
}
|
||||
TmsPriceBillRequest billReq = oracleOrderBillMapper.toBillRequest(row,
|
||||
billingProperties.getOracle().getDefaultProductCode());
|
||||
if (billReq == null || StringUtils.isEmpty(billReq.getOrderNo()))
|
||||
{
|
||||
throw new ServiceException("Oracle order mapped orderNo empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(billReq.getCustomerCode()))
|
||||
{
|
||||
throw new ServiceException("Oracle order mapped customerCode empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(billReq.getProductCode()))
|
||||
{
|
||||
throw new ServiceException("Oracle order mapped productCode empty");
|
||||
}
|
||||
if (billReq.getWeight() == null || billReq.getWeight().signum() <= 0)
|
||||
{
|
||||
throw new ServiceException("Oracle order mapped weight invalid");
|
||||
}
|
||||
// Identity keys from mobile inbound are source of truth; do not replace with Oracle remap.
|
||||
if (StringUtils.isNotEmpty(task.getTransferNo()))
|
||||
{
|
||||
billReq.setTransferNo(task.getTransferNo());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(task.getTrackingNo()))
|
||||
{
|
||||
billReq.setTrackingNo(task.getTrackingNo());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(task.getOrderNo()))
|
||||
{
|
||||
billReq.setOrderNo(task.getOrderNo());
|
||||
}
|
||||
// Billing date from task.bill_time (mobile), not Oracle CREATE_DATE.
|
||||
billReq.setBizDate(new SimpleDateFormat("yyyy-MM-dd").format(task.getBillTime()));
|
||||
|
||||
TmsPriceQuoteResult result = billWithDbRetry(taskId, billReq);
|
||||
writeback(task, BillTaskSuccessStatus.SUCCESS, result == null ? null : result.getBillId(),
|
||||
null);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
log.error("billing task failed taskId={}", taskId, e);
|
||||
writeback(task, BillTaskSuccessStatus.FAILED, null, cut(e.getMessage(), 900));
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve orderNo for REFUND from task keys or existing bill (no Oracle quote required). */
|
||||
private String resolveRefundOrderNo(PrcBillingTask task)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(task.getOrderNo()))
|
||||
{
|
||||
return task.getOrderNo().trim();
|
||||
}
|
||||
if (StringUtils.isNotEmpty(task.getTransferNo()))
|
||||
{
|
||||
PrcOrderBill byTransfer = orderBillMapper.selectPrcOrderBillByTransferNo(task.getTransferNo().trim());
|
||||
if (byTransfer != null && StringUtils.isNotEmpty(byTransfer.getOrderNo()))
|
||||
{
|
||||
return byTransfer.getOrderNo();
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(task.getTrackingNo()))
|
||||
{
|
||||
PrcOrderBill q = new PrcOrderBill();
|
||||
q.setTrackingNo(task.getTrackingNo().trim());
|
||||
List<PrcOrderBill> list = orderBillMapper.selectPrcOrderBillList(q);
|
||||
if (list != null && !list.isEmpty() && StringUtils.isNotEmpty(list.get(0).getOrderNo()))
|
||||
{
|
||||
return list.get(0).getOrderNo();
|
||||
}
|
||||
}
|
||||
throw new ServiceException("退费订单号无法解析");
|
||||
}
|
||||
|
||||
private TmsPriceQuoteResult refundWithDbRetry(Long taskId, String orderNo)
|
||||
{
|
||||
RuntimeException last = null;
|
||||
for (int attempt = 1; attempt <= BILL_DB_RETRY_MAX; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
return prcOrderBillService.refund(orderNo);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
last = e;
|
||||
if (attempt < BILL_DB_RETRY_MAX && isRetryableDbConflict(e))
|
||||
{
|
||||
long sleepMs = BILL_DB_RETRY_BASE_MS * attempt
|
||||
+ (long) (Math.random() * BILL_DB_RETRY_BASE_MS);
|
||||
log.warn("billing refund db conflict, retry taskId={} attempt={}/{} sleepMs={} msg={}",
|
||||
taskId, attempt, BILL_DB_RETRY_MAX, sleepMs, cut(e.getMessage(), 200));
|
||||
try
|
||||
{
|
||||
Thread.sleep(sleepMs);
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{
|
||||
Thread.currentThread().interrupt();
|
||||
throw e;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
throw last != null ? last : new ServiceException("billing refund retry exhausted");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry bill() on MySQL deadlock / lock wait (common when many tasks share one ctr_customer_account).
|
||||
*/
|
||||
private TmsPriceQuoteResult billWithDbRetry(Long taskId, TmsPriceBillRequest billReq)
|
||||
{
|
||||
RuntimeException last = null;
|
||||
for (int attempt = 1; attempt <= BILL_DB_RETRY_MAX; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
return prcOrderBillService.bill(billReq);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
last = e;
|
||||
if (attempt < BILL_DB_RETRY_MAX && isRetryableDbConflict(e))
|
||||
{
|
||||
long sleepMs = BILL_DB_RETRY_BASE_MS * attempt
|
||||
+ (long) (Math.random() * BILL_DB_RETRY_BASE_MS);
|
||||
log.warn("billing task db conflict, retry taskId={} attempt={}/{} sleepMs={} msg={}",
|
||||
taskId, attempt, BILL_DB_RETRY_MAX, sleepMs, cut(e.getMessage(), 200));
|
||||
try
|
||||
{
|
||||
Thread.sleep(sleepMs);
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{
|
||||
Thread.currentThread().interrupt();
|
||||
throw e;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
throw last != null ? last : new ServiceException("billing retry exhausted");
|
||||
}
|
||||
|
||||
/** Deadlock / lock wait / duplicate account insert — safe to retry whole bill txn. */
|
||||
static boolean isRetryableDbConflict(Throwable e)
|
||||
{
|
||||
Throwable t = e;
|
||||
while (t != null)
|
||||
{
|
||||
String name = t.getClass().getName();
|
||||
if (name.indexOf("DeadlockLoser") >= 0
|
||||
|| name.indexOf("CannotAcquireLock") >= 0
|
||||
|| name.indexOf("MySQLTransactionRollback") >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
String msg = t.getMessage();
|
||||
if (msg != null)
|
||||
{
|
||||
String m = msg.toLowerCase();
|
||||
if (m.indexOf("deadlock") >= 0
|
||||
|| m.indexOf("try restarting transaction") >= 0
|
||||
|| m.indexOf("lock wait timeout") >= 0
|
||||
|| m.indexOf("duplicate entry") >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
t = t.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrcBillingTask getById(Long taskId)
|
||||
{
|
||||
return billingTaskMapper.selectPrcBillingTaskById(taskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcBillingTask> selectList(PrcBillingTask query)
|
||||
{
|
||||
return billingTaskMapper.selectPrcBillingTaskList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrcBillingTask retryTask(Long taskId)
|
||||
{
|
||||
if (taskId == null)
|
||||
{
|
||||
throw new ServiceException("taskId required");
|
||||
}
|
||||
PrcBillingTask task = billingTaskMapper.selectPrcBillingTaskById(taskId);
|
||||
if (task == null)
|
||||
{
|
||||
throw new ServiceException("billing task not found");
|
||||
}
|
||||
String st = task.getSuccessStatus();
|
||||
if (BillTaskSuccessStatus.SUCCESS.getCode().equals(st))
|
||||
{
|
||||
throw new ServiceException("SUCCESS task cannot retry");
|
||||
}
|
||||
// Reset to WAITING so UI reflects re-queue; then sync process (admin expects immediate result).
|
||||
PrcBillingTask reset = new PrcBillingTask();
|
||||
reset.setTaskId(taskId);
|
||||
reset.setSuccessStatus(BillTaskSuccessStatus.WAITING.getCode());
|
||||
reset.setBillId(null);
|
||||
reset.setErrorMessage(null);
|
||||
billingTaskMapper.updateBillingResult(reset);
|
||||
|
||||
processTask(taskId);
|
||||
return billingTaskMapper.selectPrcBillingTaskById(taskId);
|
||||
}
|
||||
|
||||
private BillTaskType resolveAutoType(String transferNo, String trackingNo, String orderNo)
|
||||
{
|
||||
PrcOrderBill existed = null;
|
||||
if (StringUtils.isNotEmpty(orderNo))
|
||||
{
|
||||
existed = orderBillMapper.selectPrcOrderBillByOrderNo(orderNo);
|
||||
}
|
||||
if (existed == null && StringUtils.isNotEmpty(transferNo))
|
||||
{
|
||||
existed = orderBillMapper.selectPrcOrderBillByTransferNo(transferNo);
|
||||
}
|
||||
if (existed == null && StringUtils.isNotEmpty(trackingNo))
|
||||
{
|
||||
PrcOrderBill q = new PrcOrderBill();
|
||||
q.setTrackingNo(trackingNo);
|
||||
java.util.List<PrcOrderBill> list = orderBillMapper.selectPrcOrderBillList(q);
|
||||
if (list != null && !list.isEmpty())
|
||||
{
|
||||
existed = list.get(0);
|
||||
}
|
||||
}
|
||||
return existed == null ? BillTaskType.PENDING : BillTaskType.REBILL;
|
||||
}
|
||||
|
||||
/** Write back status/billId/error only; keep inbound transfer/tracking/order/billTime untouched. */
|
||||
private void writeback(PrcBillingTask task, BillTaskSuccessStatus status, String billId,
|
||||
String errorMessage)
|
||||
{
|
||||
PrcBillingTask upd = new PrcBillingTask();
|
||||
upd.setTaskId(task.getTaskId());
|
||||
upd.setSuccessStatus(status.getCode());
|
||||
upd.setBillId(billId);
|
||||
upd.setErrorMessage(errorMessage);
|
||||
billingTaskMapper.updateBillingResult(upd);
|
||||
}
|
||||
|
||||
private static String trim(String s)
|
||||
{
|
||||
return s == null ? null : s.trim();
|
||||
}
|
||||
|
||||
private static String cut(String s, int max)
|
||||
{
|
||||
if (s == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return s.length() <= max ? s : s.substring(0, max);
|
||||
}
|
||||
}
|
||||
+1397
File diff suppressed because it is too large
Load Diff
+2334
File diff suppressed because it is too large
Load Diff
+80
@@ -0,0 +1,80 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.MessageUtils;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.domain.PrcSurchargeRel;
|
||||
import com.bms.price.mapper.PrcSurchargeRelMapper;
|
||||
import com.bms.price.service.IPrcSurchargeRelService;
|
||||
|
||||
/**
|
||||
* 附加费与产品/明细关联 服务层实现
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
@Service
|
||||
public class PrcSurchargeRelServiceImpl implements IPrcSurchargeRelService
|
||||
{
|
||||
@Autowired
|
||||
private PrcSurchargeRelMapper relMapper;
|
||||
|
||||
@Override
|
||||
public List<PrcSurchargeRel> selectRelByProductCode(String productCode)
|
||||
{
|
||||
PrcSurchargeRel query = new PrcSurchargeRel();
|
||||
query.setProductCode(productCode);
|
||||
return relMapper.selectPrcSurchargeRelList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcSurchargeRel> selectPrcSurchargeRelList(PrcSurchargeRel rel)
|
||||
{
|
||||
return relMapper.selectPrcSurchargeRelList(rel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertPrcSurchargeRel(PrcSurchargeRel rel)
|
||||
{
|
||||
validate(rel);
|
||||
return relMapper.insertPrcSurchargeRel(rel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deletePrcSurchargeRelByIds(Long[] relIds)
|
||||
{
|
||||
if (relIds == null || relIds.length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return relMapper.deletePrcSurchargeRelByIds(relIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteRelByProductCodes(String[] productCodes)
|
||||
{
|
||||
if (productCodes == null || productCodes.length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return relMapper.deleteRelByProductCodes(productCodes);
|
||||
}
|
||||
|
||||
private void validate(PrcSurchargeRel rel)
|
||||
{
|
||||
if (rel.getScId() == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.surchargeRel.scId.empty"));
|
||||
}
|
||||
if (rel.getProductId() == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.surchargeRel.productId.empty"));
|
||||
}
|
||||
if (StringUtils.isEmpty(rel.getProductCode()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.surchargeRel.productCode.empty"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.MessageUtils;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.domain.PrcSurcharge;
|
||||
import com.bms.price.mapper.PrcSurchargeMapper;
|
||||
import com.bms.price.mapper.PrcSurchargeRelMapper;
|
||||
import com.bms.price.service.IPrcSurchargeService;
|
||||
|
||||
/**
|
||||
* 附加费配置 服务层实现
|
||||
*
|
||||
* 附加费为计费主数据,不做物理修改/删除:
|
||||
* - 编辑 = 原记录作废(ss_code=vd)+ 插入新记录(状态继承),产品关联重指向新记录(保留变更历史)
|
||||
* - 停用 = 原地修改 ss_code=of,不新增数据
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
@Service
|
||||
public class PrcSurchargeServiceImpl implements IPrcSurchargeService
|
||||
{
|
||||
/** 作废状态:区别于启用(on)/停用(of),列表默认不显示 */
|
||||
public static final String SS_CODE_VOID = "vd";
|
||||
|
||||
@Autowired
|
||||
private PrcSurchargeMapper surchargeMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcSurchargeRelMapper surchargeRelMapper;
|
||||
|
||||
@Override
|
||||
public PrcSurcharge selectPrcSurchargeById(Long scId)
|
||||
{
|
||||
return surchargeMapper.selectPrcSurchargeById(scId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcSurcharge> selectPrcSurchargeList(PrcSurcharge surcharge)
|
||||
{
|
||||
return surchargeMapper.selectPrcSurchargeList(surcharge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertPrcSurcharge(PrcSurcharge surcharge)
|
||||
{
|
||||
validate(surcharge);
|
||||
applyInsertDefaults(surcharge);
|
||||
return surchargeMapper.insertPrcSurcharge(surcharge);
|
||||
}
|
||||
|
||||
/**
|
||||
* 版本化更新:内容无变化时返回 -1(不产生新版本),否则原记录作废 + 插入新记录 + 产品关联重指向。
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public int updatePrcSurcharge(PrcSurcharge surcharge)
|
||||
{
|
||||
validate(surcharge);
|
||||
PrcSurcharge old = surchargeMapper.selectPrcSurchargeById(surcharge.getScId());
|
||||
if (old == null)
|
||||
{
|
||||
throw new ServiceException("附加费不存在或已被作废,无法修改");
|
||||
}
|
||||
// 无实际变化:不置作废、不插新记录,返回 -1 由 Controller 提示"未修改"
|
||||
if (isUnchanged(old, surcharge))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
String operator = StringUtils.isNotEmpty(surcharge.getUpdateBy()) ? surcharge.getUpdateBy() : surcharge.getCreateBy();
|
||||
// 1. 原记录作废(保留历史)
|
||||
surchargeMapper.updateSsCodeByIds(new Long[] { surcharge.getScId() }, SS_CODE_VOID, operator);
|
||||
// 2. 按表单内容插入新记录(状态继承原记录,状态变更只能走列表停用操作)
|
||||
PrcSurcharge fresh = new PrcSurcharge();
|
||||
fresh.setScName(surcharge.getScName());
|
||||
fresh.setFeeType(StringUtils.isNotEmpty(surcharge.getFeeType()) ? surcharge.getFeeType() : old.getFeeType());
|
||||
fresh.setChargeType(surcharge.getChargeType());
|
||||
fresh.setChargeValue(surcharge.getChargeValue());
|
||||
fresh.setCurrency(surcharge.getCurrency());
|
||||
fresh.setRoundMode(surcharge.getRoundMode());
|
||||
fresh.setSsCode(old.getSsCode());
|
||||
fresh.setCountryCode(surcharge.getCountryCode());
|
||||
fresh.setScNote(surcharge.getScNote());
|
||||
applyInsertDefaults(fresh);
|
||||
fresh.setCreateBy(operator);
|
||||
int rows = surchargeMapper.insertPrcSurcharge(fresh);
|
||||
// 3. 产品关联重指向新记录,保持产品附加费配置不失效
|
||||
surchargeRelMapper.rebindRelScId(surcharge.getScId(), fresh.getScId(), operator);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** 逐字段比对编辑内容与原记录是否一致(字符串 trim 后比较,数值统一 BigDecimal 比较) */
|
||||
private boolean isUnchanged(PrcSurcharge old, PrcSurcharge updated)
|
||||
{
|
||||
return eq(old.getScName(), updated.getScName())
|
||||
&& eq(old.getFeeType(), updated.getFeeType())
|
||||
&& eq(old.getChargeType(), updated.getChargeType())
|
||||
&& eqDecimal(old.getChargeValue(), updated.getChargeValue())
|
||||
&& eq(old.getCurrency(), updated.getCurrency())
|
||||
&& eq(old.getRoundMode(), updated.getRoundMode())
|
||||
&& eq(old.getCountryCode(), updated.getCountryCode())
|
||||
&& eq(old.getScNote(), updated.getScNote());
|
||||
}
|
||||
|
||||
private boolean eq(String a, String b)
|
||||
{
|
||||
String sa = a == null ? "" : a.trim();
|
||||
String sb = b == null ? "" : b.trim();
|
||||
return sa.equals(sb);
|
||||
}
|
||||
|
||||
private boolean eqDecimal(java.math.BigDecimal a, java.math.BigDecimal b)
|
||||
{
|
||||
if (a == null && b == null) return true;
|
||||
if (a == null || b == null) return false;
|
||||
return a.compareTo(b) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用(原地修改 ss_code=of,不新增数据、不动产品关联)。
|
||||
*/
|
||||
@Override
|
||||
public int disablePrcSurchargeByIds(Long[] scIds, String operator)
|
||||
{
|
||||
return updateStatusByIds(scIds, "of", operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用(原地修改 ss_code=on,不新增数据)。
|
||||
*/
|
||||
@Override
|
||||
public int enablePrcSurchargeByIds(Long[] scIds, String operator)
|
||||
{
|
||||
return updateStatusByIds(scIds, "on", operator);
|
||||
}
|
||||
|
||||
private int updateStatusByIds(Long[] scIds, String ssCode, String operator)
|
||||
{
|
||||
if (scIds == null || scIds.length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return surchargeMapper.updateSsCodeByIds(scIds, ssCode, operator);
|
||||
}
|
||||
|
||||
private void applyInsertDefaults(PrcSurcharge surcharge)
|
||||
{
|
||||
if (StringUtils.isEmpty(surcharge.getSsCode()))
|
||||
{
|
||||
surcharge.setSsCode("on");
|
||||
}
|
||||
if (StringUtils.isEmpty(surcharge.getFeeType()))
|
||||
{
|
||||
surcharge.setFeeType("SURCHARGE");
|
||||
}
|
||||
if (StringUtils.isEmpty(surcharge.getRoundMode()))
|
||||
{
|
||||
surcharge.setRoundMode("HALF_UP");
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(PrcSurcharge surcharge)
|
||||
{
|
||||
if (StringUtils.isEmpty(surcharge.getScName()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.surcharge.scName.empty"));
|
||||
}
|
||||
if (StringUtils.isEmpty(surcharge.getChargeType()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.surcharge.chargeType.empty"));
|
||||
}
|
||||
if (surcharge.getChargeValue() == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.surcharge.chargeValue.empty"));
|
||||
}
|
||||
if (surcharge.getChargeValue().compareTo(java.math.BigDecimal.ZERO) < 0)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.surcharge.chargeValue.negative"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.MessageUtils;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.domain.PrcWeightSectionDetail;
|
||||
import com.bms.price.domain.PrcWeightSectionTemplate;
|
||||
import com.bms.price.mapper.PrcWeightSectionDetailMapper;
|
||||
import com.bms.price.mapper.PrcWeightSectionTemplateMapper;
|
||||
import com.bms.price.service.IPrcWeightService;
|
||||
|
||||
/**
|
||||
* 重量段管理 服务层实现
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
@Service
|
||||
public class PrcWeightServiceImpl implements IPrcWeightService
|
||||
{
|
||||
@Autowired
|
||||
private PrcWeightSectionTemplateMapper templateMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcWeightSectionDetailMapper detailMapper;
|
||||
|
||||
@Override
|
||||
public PrcWeightSectionTemplate selectPrcWeightSectionTemplateById(Long wstId)
|
||||
{
|
||||
PrcWeightSectionTemplate template = templateMapper.selectPrcWeightSectionTemplateById(wstId);
|
||||
if (template != null)
|
||||
{
|
||||
template.setDetailCount(detailMapper.countByWstId(wstId));
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcWeightSectionTemplate> selectPrcWeightSectionTemplateList(PrcWeightSectionTemplate template)
|
||||
{
|
||||
List<PrcWeightSectionTemplate> list = templateMapper.selectPrcWeightSectionTemplateList(template);
|
||||
for (PrcWeightSectionTemplate item : list)
|
||||
{
|
||||
item.setDetailCount(detailMapper.countByWstId(item.getWstId()));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertPrcWeightSectionTemplate(PrcWeightSectionTemplate template)
|
||||
{
|
||||
if (StringUtils.isEmpty(template.getSsCode()))
|
||||
{
|
||||
template.setSsCode("on");
|
||||
}
|
||||
return templateMapper.insertPrcWeightSectionTemplate(template);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updatePrcWeightSectionTemplate(PrcWeightSectionTemplate template)
|
||||
{
|
||||
// ss_code=on 时仍允许编辑(当前阶段)
|
||||
return templateMapper.updatePrcWeightSectionTemplate(template);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deletePrcWeightSectionTemplateByIds(Long[] wstIds)
|
||||
{
|
||||
return templateMapper.deletePrcWeightSectionTemplateByIds(wstIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrcWeightSectionDetail selectPrcWeightSectionDetailById(Long wsdId)
|
||||
{
|
||||
return detailMapper.selectPrcWeightSectionDetailById(wsdId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcWeightSectionDetail> selectPrcWeightSectionDetailList(PrcWeightSectionDetail detail)
|
||||
{
|
||||
return detailMapper.selectPrcWeightSectionDetailList(detail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertPrcWeightSectionDetail(PrcWeightSectionDetail detail)
|
||||
{
|
||||
validateWeightRange(detail);
|
||||
return detailMapper.insertPrcWeightSectionDetail(detail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updatePrcWeightSectionDetail(PrcWeightSectionDetail detail)
|
||||
{
|
||||
validateWeightRange(detail);
|
||||
return detailMapper.updatePrcWeightSectionDetail(detail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deletePrcWeightSectionDetailByIds(Long[] wsdIds)
|
||||
{
|
||||
return detailMapper.deletePrcWeightSectionDetailByIds(wsdIds);
|
||||
}
|
||||
|
||||
private void validateWeightRange(PrcWeightSectionDetail detail)
|
||||
{
|
||||
if (detail.getWlStartweight() == null || detail.getWlEndweight() == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.weight.range.empty"));
|
||||
}
|
||||
if (detail.getWlStartweight().compareTo(detail.getWlEndweight()) >= 0)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.weight.range.invalid"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,892 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.DataFormatter;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.MessageUtils;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.domain.PrcZone;
|
||||
import com.bms.price.domain.PrcZoneDetail;
|
||||
import com.bms.price.domain.PrcZoneRegion;
|
||||
import com.bms.price.mapper.PrcZoneDetailMapper;
|
||||
import com.bms.price.mapper.PrcZoneMapper;
|
||||
import com.bms.price.service.IPrcZoneService;
|
||||
import com.bms.price.util.ZoneCodeAbbrevs;
|
||||
import com.bms.system.domain.BmsRegion;
|
||||
import com.bms.system.mapper.BmsRegionMapper;
|
||||
|
||||
/**
|
||||
* Zone management: bms_region -> prc_zone -> postcode detail
|
||||
*/
|
||||
@Service
|
||||
public class PrcZoneServiceImpl implements IPrcZoneService
|
||||
{
|
||||
private static final String SHEET_SUFFIX = "\u5206\u533a";
|
||||
private static final String COL_POSTCODE = "\u90ae\u7f16";
|
||||
private static final String COL_ZONE = "\u5206\u533a";
|
||||
private static final Pattern ZONE_NUM = Pattern.compile("(\\d+)");
|
||||
private static final int BATCH_SIZE = 500;
|
||||
private static final DataFormatter CELL_FMT = new DataFormatter(Locale.ROOT);
|
||||
|
||||
@Autowired
|
||||
private PrcZoneMapper zoneMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcZoneDetailMapper detailMapper;
|
||||
|
||||
@Autowired
|
||||
private BmsRegionMapper regionMapper;
|
||||
|
||||
@Override
|
||||
public PrcZoneRegion selectZoneRegionById(Long regionId)
|
||||
{
|
||||
return zoneMapper.selectZoneRegionById(regionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcZoneRegion> selectZoneRegionList(PrcZoneRegion query)
|
||||
{
|
||||
return zoneMapper.selectZoneRegionList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int deleteZonesByRegionIds(Long[] regionIds)
|
||||
{
|
||||
if (regionIds == null || regionIds.length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
for (Long regionId : regionIds)
|
||||
{
|
||||
detailMapper.deletePrcZoneDetailByRegionId(regionId);
|
||||
}
|
||||
return zoneMapper.deletePrcZoneByRegionIds(regionIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrcZone selectPrcZoneById(Long zoneId)
|
||||
{
|
||||
PrcZone zone = zoneMapper.selectPrcZoneById(zoneId);
|
||||
if (zone != null)
|
||||
{
|
||||
zone.setDetailCount(detailMapper.countByZoneId(zoneId));
|
||||
}
|
||||
return zone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcZone> selectPrcZoneList(PrcZone zone)
|
||||
{
|
||||
return zoneMapper.selectPrcZoneList(zone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertPrcZone(PrcZone zone)
|
||||
{
|
||||
if (zone.getRegionId() == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.zone.region.required"));
|
||||
}
|
||||
BmsRegion region = regionMapper.selectRegionById(zone.getRegionId());
|
||||
if (region == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.zone.region.notFound"));
|
||||
}
|
||||
if (StringUtils.isEmpty(zone.getCountryCode()))
|
||||
{
|
||||
zone.setCountryCode(region.getCountryCode2());
|
||||
}
|
||||
else
|
||||
{
|
||||
zone.setCountryCode(zone.getCountryCode().trim().toUpperCase(Locale.ROOT));
|
||||
}
|
||||
if (StringUtils.isEmpty(zone.getCountryCode()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.zone.countryCode.empty"));
|
||||
}
|
||||
if (zone.getZoneSort() == null)
|
||||
{
|
||||
zone.setZoneSort(extractZoneSort(zone.getZoneCode()));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(zone.getZoneCode()))
|
||||
{
|
||||
zone.setZoneCode(ZoneCodeAbbrevs.toAbbrev(zone.getZoneCode()));
|
||||
}
|
||||
if (StringUtils.isEmpty(zone.getZoneName()))
|
||||
{
|
||||
zone.setZoneName(buildZoneName(region.getCountryNameCn(), zone.getZoneCode()));
|
||||
}
|
||||
return zoneMapper.insertPrcZone(zone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updatePrcZone(PrcZone zone)
|
||||
{
|
||||
if (zone.getRegionId() != null && StringUtils.isEmpty(zone.getCountryCode()))
|
||||
{
|
||||
BmsRegion region = regionMapper.selectRegionById(zone.getRegionId());
|
||||
if (region != null)
|
||||
{
|
||||
zone.setCountryCode(region.getCountryCode2());
|
||||
}
|
||||
}
|
||||
else if (StringUtils.isNotEmpty(zone.getCountryCode()))
|
||||
{
|
||||
zone.setCountryCode(zone.getCountryCode().trim().toUpperCase(Locale.ROOT));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(zone.getZoneCode()))
|
||||
{
|
||||
zone.setZoneCode(ZoneCodeAbbrevs.toAbbrev(zone.getZoneCode()));
|
||||
}
|
||||
if (zone.getZoneSort() == null && StringUtils.isNotEmpty(zone.getZoneCode()))
|
||||
{
|
||||
zone.setZoneSort(extractZoneSort(zone.getZoneCode()));
|
||||
}
|
||||
return zoneMapper.updatePrcZone(zone);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int deletePrcZoneByIds(Long[] zoneIds)
|
||||
{
|
||||
return zoneMapper.deletePrcZoneByIds(zoneIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrcZoneDetail selectPrcZoneDetailById(Long detailId)
|
||||
{
|
||||
return detailMapper.selectPrcZoneDetailById(detailId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcZoneDetail> selectPrcZoneDetailList(PrcZoneDetail detail)
|
||||
{
|
||||
return detailMapper.selectPrcZoneDetailList(detail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertPrcZoneDetail(PrcZoneDetail detail)
|
||||
{
|
||||
return detailMapper.insertPrcZoneDetail(detail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updatePrcZoneDetail(PrcZoneDetail detail)
|
||||
{
|
||||
return detailMapper.updatePrcZoneDetail(detail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deletePrcZoneDetailByIds(Long[] detailIds)
|
||||
{
|
||||
return detailMapper.deletePrcZoneDetailByIds(detailIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String importZoneExcel(MultipartFile file, String operName) throws Exception
|
||||
{
|
||||
if (file == null || file.isEmpty())
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.zone.import.fileEmpty"));
|
||||
}
|
||||
String filename = file.getOriginalFilename();
|
||||
if (filename == null || !(filename.toLowerCase(Locale.ROOT).endsWith(".xls")
|
||||
|| filename.toLowerCase(Locale.ROOT).endsWith(".xlsx")))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.zone.import.fileType"));
|
||||
}
|
||||
|
||||
Map<String, BmsRegion> regionByNameCn = loadRegionByNameCn();
|
||||
if (regionByNameCn.isEmpty())
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.zone.import.regionEmpty"));
|
||||
}
|
||||
|
||||
int countryCount = 0;
|
||||
int zoneCount = 0;
|
||||
int detailCount = 0;
|
||||
List<String> importedCountries = new ArrayList<>();
|
||||
List<String> unmatched = new ArrayList<>();
|
||||
|
||||
try (InputStream is = file.getInputStream(); Workbook workbook = WorkbookFactory.create(is))
|
||||
{
|
||||
List<SheetImport> imports = findZoneImports(workbook);
|
||||
if (imports.isEmpty())
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.zone.import.noSheet"));
|
||||
}
|
||||
for (SheetImport item : imports)
|
||||
{
|
||||
Map<String, List<String>> zonePostcodes = item.zonePostcodes;
|
||||
if (zonePostcodes == null || zonePostcodes.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
BmsRegion region = resolveRegion(item.countryHint, regionByNameCn);
|
||||
if (region == null)
|
||||
{
|
||||
unmatched.add(item.countryHint + "(" + item.sheetName + ")");
|
||||
continue;
|
||||
}
|
||||
int[] counts = replaceRegionZones(region, zonePostcodes);
|
||||
countryCount += 1;
|
||||
zoneCount += counts[0];
|
||||
detailCount += counts[1];
|
||||
importedCountries.add(region.getCountryNameCn());
|
||||
}
|
||||
}
|
||||
|
||||
if (countryCount == 0)
|
||||
{
|
||||
if (!unmatched.isEmpty())
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.zone.import.regionNotFound",
|
||||
String.join(",", unmatched)));
|
||||
}
|
||||
throw new ServiceException(MessageUtils.message("price.zone.import.noData"));
|
||||
}
|
||||
String msg = MessageUtils.message("price.zone.import.success", countryCount, zoneCount, detailCount,
|
||||
String.join(",", importedCountries));
|
||||
if (!unmatched.isEmpty())
|
||||
{
|
||||
msg = msg + " " + MessageUtils.message("price.zone.import.unmatched", String.join(",", unmatched));
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
private int[] replaceRegionZones(BmsRegion region, Map<String, List<String>> zonePostcodes)
|
||||
{
|
||||
if (StringUtils.isEmpty(region.getCountryCode2()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.zone.countryCode.empty"));
|
||||
}
|
||||
Long regionId = region.getRegionId();
|
||||
detailMapper.deletePrcZoneDetailByRegionId(regionId);
|
||||
zoneMapper.deletePrcZoneByRegionId(regionId);
|
||||
|
||||
int zoneInserted = 0;
|
||||
int detailInserted = 0;
|
||||
List<Map.Entry<String, List<String>>> entries = new ArrayList<>(zonePostcodes.entrySet());
|
||||
entries.sort(Comparator.comparingInt(e -> extractZoneSort(e.getKey())));
|
||||
|
||||
String countryName = region.getCountryNameCn();
|
||||
for (Map.Entry<String, List<String>> entry : entries)
|
||||
{
|
||||
String zoneCode = ZoneCodeAbbrevs.toAbbrev(entry.getKey());
|
||||
PrcZone zone = new PrcZone();
|
||||
zone.setRegionId(regionId);
|
||||
zone.setCountryCode(region.getCountryCode2());
|
||||
zone.setZoneCode(zoneCode);
|
||||
zone.setZoneName(buildZoneName(countryName, zoneCode));
|
||||
zone.setZoneSort(extractZoneSort(entry.getKey()));
|
||||
zoneMapper.insertPrcZone(zone);
|
||||
zoneInserted++;
|
||||
|
||||
Set<String> uniquePostcodes = new LinkedHashSet<>(entry.getValue());
|
||||
List<PrcZoneDetail> batch = new ArrayList<>(BATCH_SIZE);
|
||||
for (String postcode : uniquePostcodes)
|
||||
{
|
||||
PrcZoneDetail detail = new PrcZoneDetail();
|
||||
detail.setZoneId(zone.getZoneId());
|
||||
detail.setPostcode(postcode);
|
||||
batch.add(detail);
|
||||
if (batch.size() >= BATCH_SIZE)
|
||||
{
|
||||
detailMapper.insertPrcZoneDetailBatch(batch);
|
||||
detailInserted += batch.size();
|
||||
batch = new ArrayList<>(BATCH_SIZE);
|
||||
}
|
||||
}
|
||||
if (!batch.isEmpty())
|
||||
{
|
||||
detailMapper.insertPrcZoneDetailBatch(batch);
|
||||
detailInserted += batch.size();
|
||||
}
|
||||
}
|
||||
return new int[] { zoneInserted, detailInserted };
|
||||
}
|
||||
|
||||
private static final class SheetImport
|
||||
{
|
||||
private final String sheetName;
|
||||
private final String countryHint;
|
||||
private final Map<String, List<String>> zonePostcodes;
|
||||
|
||||
private SheetImport(String sheetName, String countryHint, Map<String, List<String>> zonePostcodes)
|
||||
{
|
||||
this.sheetName = sheetName;
|
||||
this.countryHint = countryHint;
|
||||
this.zonePostcodes = zonePostcodes;
|
||||
}
|
||||
}
|
||||
|
||||
private List<SheetImport> findZoneImports(Workbook workbook)
|
||||
{
|
||||
List<SheetImport> list = new ArrayList<>();
|
||||
for (int i = 0; i < workbook.getNumberOfSheets(); i++)
|
||||
{
|
||||
Sheet sheet = workbook.getSheetAt(i);
|
||||
if (sheet == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String sheetName = StringUtils.trim(sheet.getSheetName());
|
||||
if (StringUtils.isEmpty(sheetName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String countryHint = guessCountryFromSheetName(sheetName);
|
||||
Map<String, List<String>> parsed = parseSheetByName(sheet, sheetName);
|
||||
if (parsed.isEmpty())
|
||||
{
|
||||
parsed = parseZonePostcodes(sheet);
|
||||
}
|
||||
if (parsed.isEmpty())
|
||||
{
|
||||
parsed = parseRemoteZonePostcodes(sheet);
|
||||
}
|
||||
if (parsed.isEmpty())
|
||||
{
|
||||
parsed = parseProvincePostcodes(sheet);
|
||||
}
|
||||
if (parsed.isEmpty())
|
||||
{
|
||||
parsed = parseCoveragePostcodes(sheet);
|
||||
}
|
||||
if (parsed.isEmpty())
|
||||
{
|
||||
parsed = parseUkSurchargePostcodes(sheet);
|
||||
}
|
||||
if (parsed.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.isEmpty(countryHint))
|
||||
{
|
||||
countryHint = sheetName;
|
||||
}
|
||||
list.add(new SheetImport(sheetName, countryHint, parsed));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private String guessCountryFromSheetName(String sheetName)
|
||||
{
|
||||
String name = StringUtils.trim(sheetName);
|
||||
if (name == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (name.endsWith(SHEET_SUFFIX) && name.length() > SHEET_SUFFIX.length())
|
||||
{
|
||||
return StringUtils.trim(name.substring(0, name.length() - SHEET_SUFFIX.length()));
|
||||
}
|
||||
// strip common suffixes: remote surcharge / surcharge / postcode
|
||||
String[] suffixes = {
|
||||
"\u504f\u8fdc\u9644\u52a0\u8d39",
|
||||
"\u9644\u52a0\u8d39",
|
||||
"MR\u90ae\u7f16",
|
||||
"\u90ae\u7f16"
|
||||
};
|
||||
for (String suffix : suffixes)
|
||||
{
|
||||
if (name.endsWith(suffix) && name.length() > suffix.length())
|
||||
{
|
||||
return StringUtils.trim(name.substring(0, name.length() - suffix.length()));
|
||||
}
|
||||
}
|
||||
if (name.contains("\u52a0\u62ff\u5927"))
|
||||
{
|
||||
return "\u52a0\u62ff\u5927";
|
||||
}
|
||||
if (name.contains("\u6fb3\u5927\u5229\u4e9a"))
|
||||
{
|
||||
return "\u6fb3\u5927\u5229\u4e9a";
|
||||
}
|
||||
if (name.contains("\u82f1\u56fd"))
|
||||
{
|
||||
return "\u82f1\u56fd";
|
||||
}
|
||||
if (name.contains("\u65e5\u672c"))
|
||||
{
|
||||
return "\u65e5\u672c";
|
||||
}
|
||||
if (name.contains("\u6cd5\u56fd"))
|
||||
{
|
||||
return "\u6cd5\u56fd";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, List<String>> parseSheetByName(Sheet sheet, String sheetName)
|
||||
{
|
||||
if (sheetName.endsWith(SHEET_SUFFIX))
|
||||
{
|
||||
return parseZonePostcodes(sheet);
|
||||
}
|
||||
if (sheetName.contains("\u52a0\u62ff\u5927"))
|
||||
{
|
||||
return parseRemoteZonePostcodes(sheet);
|
||||
}
|
||||
if (sheetName.contains("\u6cd5\u56fd") && sheetName.contains("\u90ae\u7f16"))
|
||||
{
|
||||
return parseCoveragePostcodes(sheet);
|
||||
}
|
||||
if (sheetName.contains("\u65e5\u672c"))
|
||||
{
|
||||
return parseProvincePostcodes(sheet);
|
||||
}
|
||||
if (sheetName.contains("\u82f1\u56fd"))
|
||||
{
|
||||
return parseUkSurchargePostcodes(sheet);
|
||||
}
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
private List<Sheet> findZoneSheets(Workbook workbook)
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private String parseCountryNameFromSheet(String sheetName)
|
||||
{
|
||||
return guessCountryFromSheetName(sheetName);
|
||||
}
|
||||
|
||||
/** Australia style: postcode + zone */
|
||||
private Map<String, List<String>> parseZonePostcodes(Sheet sheet)
|
||||
{
|
||||
return parseTwoColZoneMap(sheet, true);
|
||||
}
|
||||
|
||||
/** Canada style: remote postcode + remote zone */
|
||||
private Map<String, List<String>> parseRemoteZonePostcodes(Sheet sheet)
|
||||
{
|
||||
Map<String, List<String>> map = new LinkedHashMap<>();
|
||||
int postcodeCol = -1;
|
||||
int zoneCol = -1;
|
||||
int headerRow = -1;
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
for (int r = 0; r <= Math.min(lastRow, 20); r++)
|
||||
{
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int c = 0; c < row.getLastCellNum(); c++)
|
||||
{
|
||||
String val = cellText(row.getCell(c));
|
||||
if (val.contains("\u90ae\u7f16") && postcodeCol < 0)
|
||||
{
|
||||
postcodeCol = c;
|
||||
headerRow = r;
|
||||
}
|
||||
if ((val.equals("\u504f\u8fdc\u5206\u533a") || val.equals(COL_ZONE) || val.contains("\u5206\u533a"))
|
||||
&& !val.contains("\u9644\u52a0") && zoneCol < 0)
|
||||
{
|
||||
zoneCol = c;
|
||||
headerRow = r;
|
||||
}
|
||||
}
|
||||
if (postcodeCol >= 0 && zoneCol >= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (postcodeCol < 0 || zoneCol < 0 || headerRow < 0)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
fillTwoCol(map, sheet, headerRow, postcodeCol, zoneCol);
|
||||
return map;
|
||||
}
|
||||
|
||||
/** Japan style: province + postcode */
|
||||
private Map<String, List<String>> parseProvincePostcodes(Sheet sheet)
|
||||
{
|
||||
Map<String, List<String>> map = new LinkedHashMap<>();
|
||||
int provinceCol = -1;
|
||||
int postcodeCol = -1;
|
||||
int headerRow = -1;
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
for (int r = 0; r <= Math.min(lastRow, 10); r++)
|
||||
{
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int c = 0; c < row.getLastCellNum(); c++)
|
||||
{
|
||||
String val = cellText(row.getCell(c));
|
||||
if ((val.contains("\u5dde") || val.contains("\u7701") || "\u5dde\u7701".equals(val)) && provinceCol < 0)
|
||||
{
|
||||
provinceCol = c;
|
||||
headerRow = r;
|
||||
}
|
||||
if (COL_POSTCODE.equals(val) || val.equals("\u90ae\u7f16"))
|
||||
{
|
||||
postcodeCol = c;
|
||||
headerRow = r;
|
||||
}
|
||||
}
|
||||
if (provinceCol >= 0 && postcodeCol >= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (provinceCol < 0 || postcodeCol < 0 || headerRow < 0)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
// zone = province, detail = postcode (swap vs AU)
|
||||
for (int r = headerRow + 1; r <= lastRow; r++)
|
||||
{
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String zoneCode = normalizeCell(cellText(row.getCell(provinceCol)));
|
||||
String postcode = normalizePostcode(cellText(row.getCell(postcodeCol)));
|
||||
if (StringUtils.isEmpty(zoneCode) || StringUtils.isEmpty(postcode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
map.computeIfAbsent(zoneCode, k -> new ArrayList<>()).add(postcode);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** France MR style: carrier/zone + coverage postcode */
|
||||
private Map<String, List<String>> parseCoveragePostcodes(Sheet sheet)
|
||||
{
|
||||
Map<String, List<String>> map = new LinkedHashMap<>();
|
||||
int zoneCol = -1;
|
||||
int postcodeCol = -1;
|
||||
int headerRow = -1;
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
for (int r = 0; r <= Math.min(lastRow, 10); r++)
|
||||
{
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int c = 0; c < row.getLastCellNum(); c++)
|
||||
{
|
||||
String val = cellText(row.getCell(c));
|
||||
if (val.contains("\u8986\u76d6\u90ae\u7f16") || val.equals(COL_POSTCODE))
|
||||
{
|
||||
postcodeCol = c;
|
||||
headerRow = r;
|
||||
}
|
||||
if (val.contains("\u5c3e\u7a0b") || val.contains("\u81ea\u63d0"))
|
||||
{
|
||||
zoneCol = c;
|
||||
headerRow = r;
|
||||
}
|
||||
}
|
||||
if (zoneCol >= 0 && postcodeCol >= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (postcodeCol < 0)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
if (zoneCol < 0)
|
||||
{
|
||||
zoneCol = 0;
|
||||
}
|
||||
if (headerRow < 0)
|
||||
{
|
||||
headerRow = 0;
|
||||
}
|
||||
for (int r = headerRow + 1; r <= lastRow; r++)
|
||||
{
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String zoneCode = normalizeCell(cellText(row.getCell(zoneCol)));
|
||||
if (StringUtils.isEmpty(zoneCode))
|
||||
{
|
||||
zoneCode = "Mondial Relay";
|
||||
}
|
||||
String postcode = normalizePostcode(cellText(row.getCell(postcodeCol)));
|
||||
if (StringUtils.isEmpty(postcode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
map.computeIfAbsent(zoneCode, k -> new ArrayList<>()).add(postcode);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** UK surcharge sheet: multi blocks of region + postcode prefix */
|
||||
private Map<String, List<String>> parseUkSurchargePostcodes(Sheet sheet)
|
||||
{
|
||||
Map<String, List<String>> map = new LinkedHashMap<>();
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
// detect header row containing ????
|
||||
int headerRow = -1;
|
||||
for (int r = 0; r <= Math.min(lastRow, 10); r++)
|
||||
{
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int c = 0; c < row.getLastCellNum(); c++)
|
||||
{
|
||||
if (cellText(row.getCell(c)).contains("\u90ae\u7f16\u5f00\u5934")
|
||||
|| cellText(row.getCell(c)).contains("\u7a7a\u6c14\u51cf\u6392"))
|
||||
{
|
||||
headerRow = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (headerRow >= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (headerRow < 0)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
String lastRm = "";
|
||||
for (int r = headerRow + 1; r <= lastRow; r++)
|
||||
{
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String rmName = normalizeCell(cellText(row.getCell(0)));
|
||||
if (StringUtils.isNotEmpty(rmName))
|
||||
{
|
||||
lastRm = rmName;
|
||||
}
|
||||
String rmPc = normalizePostcode(cellText(row.getCell(1)));
|
||||
if (StringUtils.isNotEmpty(lastRm) && StringUtils.isNotEmpty(rmPc))
|
||||
{
|
||||
map.computeIfAbsent(lastRm, k -> new ArrayList<>()).add(rmPc);
|
||||
}
|
||||
String evName = normalizeCell(cellText(row.getCell(5)));
|
||||
if (StringUtils.isEmpty(evName))
|
||||
{
|
||||
evName = normalizeCell(cellText(row.getCell(4)));
|
||||
}
|
||||
String evPc = normalizePostcode(cellText(row.getCell(6)));
|
||||
if (StringUtils.isNotEmpty(evName) && StringUtils.isNotEmpty(evPc))
|
||||
{
|
||||
map.computeIfAbsent(evName, k -> new ArrayList<>()).add(evPc);
|
||||
}
|
||||
String airName = normalizeCell(cellText(row.getCell(8)));
|
||||
String airPc = normalizePostcode(cellText(row.getCell(9)));
|
||||
if (StringUtils.isNotEmpty(airName) && StringUtils.isNotEmpty(airPc))
|
||||
{
|
||||
map.computeIfAbsent(airName, k -> new ArrayList<>()).add(airPc);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, List<String>> parseTwoColZoneMap(Sheet sheet, boolean requireExactPostcode)
|
||||
{
|
||||
Map<String, List<String>> map = new LinkedHashMap<>();
|
||||
int postcodeCol = -1;
|
||||
int zoneCol = -1;
|
||||
int headerRow = -1;
|
||||
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
for (int r = 0; r <= Math.min(lastRow, 20); r++)
|
||||
{
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int c = 0; c < row.getLastCellNum(); c++)
|
||||
{
|
||||
String val = cellText(row.getCell(c));
|
||||
if (COL_POSTCODE.equals(val) || (!requireExactPostcode && val.contains(COL_POSTCODE)))
|
||||
{
|
||||
postcodeCol = c;
|
||||
headerRow = r;
|
||||
}
|
||||
else if (COL_ZONE.equals(val))
|
||||
{
|
||||
zoneCol = c;
|
||||
headerRow = r;
|
||||
}
|
||||
}
|
||||
if (postcodeCol >= 0 && zoneCol >= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (postcodeCol < 0 || zoneCol < 0 || headerRow < 0)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
fillTwoCol(map, sheet, headerRow, postcodeCol, zoneCol);
|
||||
return map;
|
||||
}
|
||||
|
||||
private void fillTwoCol(Map<String, List<String>> map, Sheet sheet, int headerRow, int postcodeCol, int zoneCol)
|
||||
{
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
for (int r = headerRow + 1; r <= lastRow; r++)
|
||||
{
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String postcode = normalizePostcode(cellText(row.getCell(postcodeCol)));
|
||||
String zoneCode = normalizeCell(cellText(row.getCell(zoneCol)));
|
||||
if (StringUtils.isEmpty(postcode) || StringUtils.isEmpty(zoneCode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
map.computeIfAbsent(zoneCode, k -> new ArrayList<>()).add(postcode);
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalizePostcode(String postcode)
|
||||
{
|
||||
if (StringUtils.isEmpty(postcode))
|
||||
{
|
||||
return postcode;
|
||||
}
|
||||
if (postcode.endsWith(".0") && postcode.matches("\\d+\\.0"))
|
||||
{
|
||||
return postcode.substring(0, postcode.length() - 2);
|
||||
}
|
||||
return postcode;
|
||||
}
|
||||
|
||||
private static String normalizeCell(String text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return text.replace('\r', ' ').replace('\n', ' ').replaceAll("\\s+", " ").trim();
|
||||
}
|
||||
|
||||
private String cellText(Cell cell)
|
||||
{
|
||||
if (cell == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return StringUtils.trim(CELL_FMT.formatCellValue(cell));
|
||||
}
|
||||
|
||||
private Map<String, BmsRegion> loadRegionByNameCn()
|
||||
{
|
||||
BmsRegion query = new BmsRegion();
|
||||
List<BmsRegion> regions = regionMapper.selectRegionList(query);
|
||||
Map<String, BmsRegion> map = new HashMap<>();
|
||||
if (regions == null)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
for (BmsRegion region : regions)
|
||||
{
|
||||
if (region != null && StringUtils.isNotEmpty(region.getCountryNameCn()))
|
||||
{
|
||||
map.put(region.getCountryNameCn().trim(), region);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private BmsRegion resolveRegion(String excelName, Map<String, BmsRegion> regionByNameCn)
|
||||
{
|
||||
if (StringUtils.isEmpty(excelName) || regionByNameCn == null || regionByNameCn.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String name = excelName.trim();
|
||||
BmsRegion exact = regionByNameCn.get(name);
|
||||
if (exact != null)
|
||||
{
|
||||
return exact;
|
||||
}
|
||||
BmsRegion best = null;
|
||||
int bestLen = -1;
|
||||
for (BmsRegion region : regionByNameCn.values())
|
||||
{
|
||||
String cn = region.getCountryNameCn();
|
||||
if (StringUtils.isEmpty(cn))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (name.contains(cn) || cn.contains(name))
|
||||
{
|
||||
if (cn.length() > bestLen)
|
||||
{
|
||||
best = region;
|
||||
bestLen = cn.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private static String buildZoneName(String countryName, String zoneCode)
|
||||
{
|
||||
return (countryName == null ? "" : countryName) + "(" + zoneCode + ")";
|
||||
}
|
||||
|
||||
private static int extractZoneSort(String zoneCode)
|
||||
{
|
||||
if (StringUtils.isEmpty(zoneCode))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
Matcher m = ZONE_NUM.matcher(zoneCode);
|
||||
if (m.find())
|
||||
{
|
||||
try
|
||||
{
|
||||
return Integer.parseInt(m.group(1));
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+1241
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user