应付账单初始化
This commit is contained in:
@@ -27,6 +27,11 @@ public interface ServiceChannelMapper
|
||||
*/
|
||||
ServiceChannel selectServiceChannelByCode(String channelCode);
|
||||
|
||||
/**
|
||||
* 按渠道名称精确查询,重名时返回多条供调用方拒绝。
|
||||
*/
|
||||
List<ServiceChannel> selectServiceChannelByName(String channelName);
|
||||
|
||||
/**
|
||||
* 产品渠道关系中引用该渠道编码的数量
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 渠道和产品的收付关系。payType 取字典 bms_pay_type。
|
||||
*/
|
||||
@Data
|
||||
public class ChannelProductMap
|
||||
{
|
||||
private Long id;
|
||||
|
||||
/** 渠道表主键 srv_service_channel.id */
|
||||
private String channelId;
|
||||
|
||||
private Long productId;
|
||||
|
||||
/** 应收套餐 prc_price_set.price_set_id */
|
||||
private Long priceSetId;
|
||||
|
||||
/** AR 应收,AP 应付 */
|
||||
private String payType;
|
||||
|
||||
private String createBy;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
private String updateBy;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 应付账单。作废后仍保留。 */
|
||||
@Data
|
||||
public class PayBill
|
||||
{
|
||||
private Long billId;
|
||||
|
||||
private String billNo;
|
||||
|
||||
private Long supplierId;
|
||||
|
||||
private String supplierName;
|
||||
|
||||
private String currency;
|
||||
|
||||
/** ORDER 订单号,TIME 计费日,LADING 提单 */
|
||||
private String sourceType;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date periodStart;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date periodEnd;
|
||||
|
||||
private Integer orderCount;
|
||||
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
/** DRAFT CONFIRMED PAID VOID */
|
||||
private String status;
|
||||
|
||||
private String voidReason;
|
||||
|
||||
private String voidBy;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date voidTime;
|
||||
|
||||
private String createBy;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date createTime;
|
||||
|
||||
private String updateBy;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date updateTime;
|
||||
|
||||
private String remark;
|
||||
|
||||
private List<PayBillLine> lines;
|
||||
|
||||
private List<PayBillLog> logs;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/** 查询或生成应付账单。订单号和提单号可用逗号、空格、换行分隔。 */
|
||||
@Data
|
||||
public class PayBillCommand
|
||||
{
|
||||
/** ORDER、TIME、LADING */
|
||||
private String sourceType;
|
||||
|
||||
private String orderText;
|
||||
|
||||
private String beginDate;
|
||||
|
||||
private String endDate;
|
||||
|
||||
private String ladingText;
|
||||
|
||||
private Long supplierId;
|
||||
|
||||
private String remark;
|
||||
|
||||
private String voidReason;
|
||||
|
||||
private Long billId;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 应付账单上的一条费用快照。 */
|
||||
@Data
|
||||
public class PayBillLine
|
||||
{
|
||||
private Long lineId;
|
||||
|
||||
private Long billId;
|
||||
|
||||
private String orderBillId;
|
||||
|
||||
private String orderNo;
|
||||
|
||||
private String ladingNo;
|
||||
|
||||
private Long ladingFeeId;
|
||||
|
||||
private String feeName;
|
||||
|
||||
private BigDecimal amount;
|
||||
|
||||
private String currency;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date priceDate;
|
||||
|
||||
private Integer versionNo;
|
||||
|
||||
private String remoteFlag;
|
||||
|
||||
private String densityLevel;
|
||||
|
||||
/** ORDER 订单费用,LADING 留在主单上的费用 */
|
||||
private String lineSource;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 应付账单操作记录。作废后不删除。 */
|
||||
@Data
|
||||
public class PayBillLog
|
||||
{
|
||||
private Long logId;
|
||||
|
||||
private Long billId;
|
||||
|
||||
private String operationType;
|
||||
|
||||
private String fromStatus;
|
||||
|
||||
private String toStatus;
|
||||
|
||||
private String operationDesc;
|
||||
|
||||
private String operatorName;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/** 生成应付账单前查到的一票订单。 */
|
||||
@Data
|
||||
public class PayBillOrder
|
||||
{
|
||||
private String billId;
|
||||
|
||||
private String orderNo;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date bizDate;
|
||||
|
||||
private BigDecimal payTotal;
|
||||
|
||||
private String currency;
|
||||
|
||||
private String ladingNo;
|
||||
|
||||
private String trackingNo;
|
||||
|
||||
private String channelName;
|
||||
|
||||
private String productName;
|
||||
|
||||
private String supplierName;
|
||||
|
||||
private Integer pieces;
|
||||
|
||||
private BigDecimal chargeWeight;
|
||||
|
||||
private String countryCode;
|
||||
|
||||
private String countryName;
|
||||
|
||||
private String destPostcode;
|
||||
|
||||
/** 已算出的应付费用,名称和金额用分号拼在一起 */
|
||||
private String feeText;
|
||||
|
||||
/** 已挂在未作废账单上时的账单号 */
|
||||
private String occupiedBillNo;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/** 应付账单生成前的查询结果。 */
|
||||
@Data
|
||||
public class PayBillPreview
|
||||
{
|
||||
private List<PayBillOrder> orders = new ArrayList<>();
|
||||
|
||||
private List<String> missingNos = new ArrayList<>();
|
||||
|
||||
private List<PayBillOrder> occupied = new ArrayList<>();
|
||||
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
/** 一条算出的应付费用。 */
|
||||
@Data
|
||||
public class PayCostLine
|
||||
{
|
||||
private String feeType;
|
||||
|
||||
private String feeName;
|
||||
|
||||
private Long scId;
|
||||
|
||||
private BigDecimal amount;
|
||||
|
||||
private String currency;
|
||||
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 产品报价表里的邮编或其他不能落进重量段、附加费的配置原文。
|
||||
*/
|
||||
@Data
|
||||
public class PayExtraLine
|
||||
{
|
||||
private Long lineId;
|
||||
|
||||
private Long productId;
|
||||
|
||||
/** 邮编 或 配置 */
|
||||
private String lineType;
|
||||
|
||||
private String lineText;
|
||||
|
||||
private Integer sortNo;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/** 按订单生成应付的结果。skipped 是价卡里有、但这票算不了的附加费。 */
|
||||
@Data
|
||||
public class PayOrderCostResult
|
||||
{
|
||||
private String billId;
|
||||
|
||||
private String channelId;
|
||||
|
||||
private String channelName;
|
||||
|
||||
private Long productId;
|
||||
|
||||
private String productName;
|
||||
|
||||
private Integer versionNo;
|
||||
|
||||
private String currency;
|
||||
|
||||
private BigDecimal payTotal;
|
||||
|
||||
private String message;
|
||||
|
||||
private List<PayCostLine> fees;
|
||||
|
||||
private List<String> skipped;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.bms.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 应付邮编配置的一条分区或一条合并后的附加费。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PayPostcodeGroup extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long groupId;
|
||||
|
||||
private String sourceFile;
|
||||
|
||||
private String fileMd5;
|
||||
|
||||
private String sheetName;
|
||||
|
||||
private String blockTitle;
|
||||
|
||||
/** ZONE 邮编分区,FEE 附加费 */
|
||||
private String configKind;
|
||||
|
||||
/** REACH 可达,UNREACH 不可达,REMOTE 偏远。普通分区为空 */
|
||||
private String coverage;
|
||||
|
||||
/** 渠道名称,从表头识别,可在页面修改 */
|
||||
private String channelName;
|
||||
|
||||
/** 目录里的服务类型,和报价产品对应 */
|
||||
private String serviceType;
|
||||
|
||||
/** 目录里的国家。邮编目的国仍在 countryName,偏远表里两者可以不同 */
|
||||
private String catalogCountry;
|
||||
|
||||
/** 目录里的销售产品 */
|
||||
private String productName;
|
||||
|
||||
/** 目录里的报价工作表 */
|
||||
private String quoteSheet;
|
||||
|
||||
private String countryName;
|
||||
|
||||
private String countryCode;
|
||||
|
||||
private String feeName;
|
||||
|
||||
private String feeText;
|
||||
|
||||
private Integer lineCount;
|
||||
|
||||
/** 查询条件:邮编 */
|
||||
private String postcode;
|
||||
|
||||
/** 导入时挂上的明细,列表查询不填充 */
|
||||
private List<PayPostcodeLine> lines = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
|
||||
/** 一次 Excel 导入的汇总,不把全部邮编返回给页面。 */
|
||||
@Data
|
||||
public class PayPostcodeImportResult implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String sourceFile;
|
||||
|
||||
private int groupCount;
|
||||
|
||||
private int feeCount;
|
||||
|
||||
private int zoneCount;
|
||||
|
||||
private int lineCount;
|
||||
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 一条邮编。除末尾星号外,地区、分区和金额都保持 Excel 原文。
|
||||
*/
|
||||
@Data
|
||||
public class PayPostcodeLine implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long lineId;
|
||||
|
||||
private Long groupId;
|
||||
|
||||
private String countryName;
|
||||
|
||||
private String countryCode;
|
||||
|
||||
private String regionName;
|
||||
|
||||
private String regionNameCn;
|
||||
|
||||
private String cityName;
|
||||
|
||||
private String regionCode;
|
||||
|
||||
private String zoneName;
|
||||
|
||||
private String postcode;
|
||||
|
||||
private String postcodeRaw;
|
||||
|
||||
private String postcodeEnd;
|
||||
|
||||
private String extraText;
|
||||
|
||||
private Integer sortNo;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 导入预览。此时还没有写入数据库。
|
||||
*/
|
||||
@Data
|
||||
public class PayPreview
|
||||
{
|
||||
private String sourceFile;
|
||||
|
||||
private String fileMd5;
|
||||
|
||||
private String supplierName;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date effectiveDate;
|
||||
|
||||
private List<PayProduct> products = new ArrayList<>();
|
||||
|
||||
/** 没有对上字典、因此没有读取的工作表 */
|
||||
private List<String> skippedSheets = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/** 旧版和新版价格对照。 */
|
||||
@Data
|
||||
public class PayPriceCompare
|
||||
{
|
||||
private PayProduct older;
|
||||
|
||||
private PayProduct newer;
|
||||
|
||||
private List<PayPriceDiff> weightDiffs;
|
||||
|
||||
private List<PayPriceDiff> surchargeDiffs;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/** 两个价格版本里的一行差异。 */
|
||||
@Data
|
||||
public class PayPriceDiff
|
||||
{
|
||||
private String name;
|
||||
|
||||
private String zoneName;
|
||||
|
||||
/** 相同、变更、新增、删除。新增和删除相对新版本而言 */
|
||||
private String change;
|
||||
|
||||
private String oldValue;
|
||||
|
||||
private String newValue;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.bms.common.annotation.Excel;
|
||||
import com.bms.common.core.domain.BaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 应付价卡产品。目录里的一个产品对应一行,重量段和附加费在子表。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PayProduct extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long productId;
|
||||
|
||||
@Excel(name = "产品名称", width = 24)
|
||||
private String productName;
|
||||
|
||||
/** 支付类型,字典 bms_pay_type:AR应收 AP应付 */
|
||||
private String productType;
|
||||
|
||||
/** 字典 payment_product_type 的键值,和报价工作表对应 */
|
||||
private String dictValue;
|
||||
|
||||
/** 复制来源。空表示这是公共模板 */
|
||||
private Long originId;
|
||||
|
||||
/** 服务渠道 ID。空表示公共模板 */
|
||||
private String channelId;
|
||||
|
||||
/** 版本号。模板是 1,渠道另存从 2 起 */
|
||||
private Integer versionNo;
|
||||
|
||||
/** 列表只看公共模板 */
|
||||
private Boolean templateOnly;
|
||||
|
||||
/** 列表每个产品只显示当前生效版;没有生效版时显示最新一版 */
|
||||
private Boolean currentOnly;
|
||||
|
||||
/** 当前价格版本对应的记录。列表上的 productId 是不变的产品编号 */
|
||||
private Long versionProductId;
|
||||
|
||||
/** 当前版本的有效期记录 */
|
||||
private Long termId;
|
||||
|
||||
/** 0 生效,1 失效 */
|
||||
private String termStatus;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date termStart;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date termEnd;
|
||||
|
||||
@Excel(name = "国家/口岸", width = 18)
|
||||
private String countryScope;
|
||||
|
||||
/** 字典 order_charge_type:续重计费、单票价、常规计重价 */
|
||||
@Excel(name = "费用类型", width = 14)
|
||||
private String chargeType;
|
||||
|
||||
/** 续重时每公斤加价。各分区相同是一个金额,不同是最小~最大 */
|
||||
@Excel(name = "每公斤加价", width = 14)
|
||||
private String extraPerKg;
|
||||
|
||||
@Excel(name = "服务类型", width = 16)
|
||||
private String serviceType;
|
||||
|
||||
@Excel(name = "分类", width = 16)
|
||||
private String classification;
|
||||
|
||||
@Excel(name = "目录备注", width = 28)
|
||||
private String catalogRemark;
|
||||
|
||||
@Excel(name = "更新内容", width = 18)
|
||||
private String updateContent;
|
||||
|
||||
@Excel(name = "重量限制", width = 14)
|
||||
private String weightLimit;
|
||||
|
||||
@Excel(name = "报价工作表", width = 24)
|
||||
private String sheetName;
|
||||
|
||||
@Excel(name = "关联分区表", width = 24)
|
||||
private String relatedSheet;
|
||||
|
||||
@Excel(name = "来源文件", width = 36)
|
||||
private String sourceFile;
|
||||
|
||||
private String fileMd5;
|
||||
|
||||
@Excel(name = "价卡归属", width = 12)
|
||||
private String supplierName;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
@Excel(name = "生效日期", width = 14, dateFormat = "yyyy-MM-dd")
|
||||
private Date effectiveDate;
|
||||
|
||||
@Excel(name = "币种", width = 10)
|
||||
private String currency;
|
||||
|
||||
@Excel(name = "服务说明", width = 40)
|
||||
private String serviceNote;
|
||||
|
||||
@Excel(name = "识别结果", width = 12)
|
||||
private String parseStatus;
|
||||
|
||||
@Excel(name = "重量段数", width = 12)
|
||||
private Integer weightCount;
|
||||
|
||||
@Excel(name = "附加费数", width = 12)
|
||||
private Integer surchargeCount;
|
||||
|
||||
@Excel(name = "其他配置数", width = 12)
|
||||
private Integer extraCount;
|
||||
|
||||
/** 查询条件:生效日起,格式 yyyy-MM-dd */
|
||||
private String effectiveBegin;
|
||||
|
||||
/** 查询条件:生效日止 */
|
||||
private String effectiveEnd;
|
||||
|
||||
/** 查询条件:附加费名称、收费标准、费用说明或时效原文 */
|
||||
private String surchargeName;
|
||||
|
||||
/** 查询条件:重量段、费用项、分区或国家 */
|
||||
private String weightKeyword;
|
||||
|
||||
/** 预览时标记库里是否已有同一文件、同一工作表、同一产品 */
|
||||
private Boolean duplicated;
|
||||
|
||||
/** 已存在记录的主键,覆盖保存时使用 */
|
||||
private Long existsId;
|
||||
|
||||
private List<PayWeightBand> weightBands;
|
||||
|
||||
private List<PaySurcharge> surcharges;
|
||||
|
||||
private List<PayExtraLine> extraLines;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import com.bms.common.core.domain.BaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 应付产品某一版价格的有效期。同一产品多次导入,每版一行。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PayProductTerm extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long termId;
|
||||
|
||||
/** 字典 payment_product_type,同一产品的各版本用它归组 */
|
||||
private String dictValue;
|
||||
|
||||
private Long productId;
|
||||
|
||||
private Integer versionNo;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date effectiveStart;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date effectiveEnd;
|
||||
|
||||
/** 0 生效,1 失效 */
|
||||
private String status;
|
||||
|
||||
private String productName;
|
||||
|
||||
private String sourceFile;
|
||||
|
||||
private Integer weightCount;
|
||||
|
||||
private Integer surchargeCount;
|
||||
|
||||
private String currency;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 预览确认后的保存请求。只保存前端勾选并回传的产品。
|
||||
*/
|
||||
@Data
|
||||
public class PaySaveCommand
|
||||
{
|
||||
/** 为 true 时,同文件同表同名的旧产品先删后插 */
|
||||
private boolean overwrite;
|
||||
|
||||
private List<PayProduct> products = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 应付产品附加费。生效开始和结束都空时,表示跟随价卡生效日,不另设窗口。
|
||||
*/
|
||||
@Data
|
||||
public class PaySurcharge
|
||||
{
|
||||
private Long surchargeId;
|
||||
|
||||
private Long productId;
|
||||
|
||||
/** 所属价卡版本,跟产品版本一起变 */
|
||||
private Integer versionNo;
|
||||
|
||||
private String sectionName;
|
||||
|
||||
private String feeName;
|
||||
|
||||
private String zoneName;
|
||||
|
||||
/** 收费标准原文 */
|
||||
private String chargeText;
|
||||
|
||||
/** 从收费标准里解析出的金额;百分比时这里是百分数 */
|
||||
private BigDecimal amount;
|
||||
|
||||
private String currency;
|
||||
|
||||
/** 费用说明 */
|
||||
private String feeDesc;
|
||||
|
||||
/** 时效原文,解析不出日期时仍保留 */
|
||||
private String periodText;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date effectiveStart;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private Date effectiveEnd;
|
||||
|
||||
/** 固定收取、产生时收取、旺季期间 */
|
||||
private String chargeTiming;
|
||||
|
||||
/** ALWAYS、POSTCODE、WEIGHT、SIZE、RETURN、MANUAL */
|
||||
private String calcRule;
|
||||
|
||||
/** 邮编列表、重量阈值或尺寸表达式 */
|
||||
private String calcParam;
|
||||
|
||||
private Integer sortNo;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 应付产品上的一行重量段、首重续重或分区单价。
|
||||
*/
|
||||
@Data
|
||||
public class PayWeightBand
|
||||
{
|
||||
private Long bandId;
|
||||
|
||||
private Long productId;
|
||||
|
||||
/** 所属价卡版本,跟产品版本一起变 */
|
||||
private Integer versionNo;
|
||||
|
||||
/** 报价区块,例如派送费、偏远附加费 */
|
||||
private String sectionName;
|
||||
|
||||
private String countryName;
|
||||
|
||||
private String countryCode;
|
||||
|
||||
/** 价格列名称,例如英国大陆、马德里高密度 */
|
||||
private String zoneName;
|
||||
|
||||
/** 重量段原文 */
|
||||
private String weightLabel;
|
||||
|
||||
private BigDecimal weightMin;
|
||||
|
||||
private BigDecimal weightMax;
|
||||
|
||||
/** 体积上限,数字加单位,如 10000cm³。空表示这档不看体积 */
|
||||
private String volumeText;
|
||||
|
||||
/** 处理费、派送费、续重等 */
|
||||
private String chargeName;
|
||||
|
||||
private BigDecimal price;
|
||||
|
||||
/** EUR/ITEM、GBP/KG 等 */
|
||||
private String priceUnit;
|
||||
|
||||
private Integer sortNo;
|
||||
}
|
||||
@@ -23,6 +23,9 @@ public class PrcPayBillFee implements Serializable
|
||||
private String currency;
|
||||
private String roundMode;
|
||||
private String remark;
|
||||
|
||||
/** CALC 计算,LADING 提单分摊,MANUAL 人工 */
|
||||
private String feeSource;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
|
||||
@@ -35,4 +35,6 @@ public class PrcProfitReportQuery extends BaseEntity
|
||||
private String arBillStatus;
|
||||
/** 是否按产品维度分组值下钻订单明细,允许分组字段为空。 */
|
||||
private Boolean productDrilldown;
|
||||
/** 仅用于拒绝已暂停的提成未计算订单下钻请求。 */
|
||||
private String detailType;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.ChannelProductMap;
|
||||
|
||||
/**
|
||||
* 渠道产品关系映射
|
||||
*/
|
||||
public interface ChannelProductMapMapper
|
||||
{
|
||||
ChannelProductMap selectByChannelAndType(@Param("channelId") String channelId, @Param("payType") String payType);
|
||||
|
||||
int upsert(ChannelProductMap row);
|
||||
|
||||
int deleteByChannelAndType(@Param("channelId") String channelId, @Param("payType") String payType);
|
||||
|
||||
int deleteByProductId(@Param("productId") Long productId);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.PayBill;
|
||||
import com.bms.price.domain.PayBillLine;
|
||||
import com.bms.price.domain.PayBillLog;
|
||||
import com.bms.price.domain.PayBillOrder;
|
||||
import com.bms.price.domain.PrcPayBillFee;
|
||||
|
||||
public interface PayBillMapper
|
||||
{
|
||||
List<PayBill> selectPayBillList(PayBill query);
|
||||
|
||||
PayBill selectPayBillById(Long billId);
|
||||
|
||||
List<PayBillLine> selectLines(Long billId);
|
||||
|
||||
List<PayBillLog> selectLogs(Long billId);
|
||||
|
||||
List<PayBillOrder> selectCandidateOrders(@Param("orderNos") List<String> orderNos, @Param("beginDate") String beginDate,
|
||||
@Param("endDate") String endDate, @Param("ladingNos") List<String> ladingNos);
|
||||
|
||||
List<PrcPayBillFee> selectOrderFees(@Param("billIds") List<String> billIds);
|
||||
|
||||
int insertPayBill(PayBill bill);
|
||||
|
||||
int updatePayBillStatus(PayBill bill);
|
||||
|
||||
int insertLines(@Param("list") List<PayBillLine> list);
|
||||
|
||||
int insertLog(PayBillLog log);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.PayPostcodeGroup;
|
||||
import com.bms.price.domain.PayPostcodeLine;
|
||||
|
||||
/** 应付邮编配置 */
|
||||
public interface PayPostcodeMapper
|
||||
{
|
||||
List<PayPostcodeGroup> selectGroupList(PayPostcodeGroup query);
|
||||
|
||||
PayPostcodeGroup selectGroupById(Long groupId);
|
||||
|
||||
int updateGroup(PayPostcodeGroup group);
|
||||
|
||||
List<PayPostcodeLine> selectLineList(PayPostcodeLine query);
|
||||
|
||||
int insertGroup(PayPostcodeGroup group);
|
||||
|
||||
int insertLines(@Param("list") List<PayPostcodeLine> list);
|
||||
|
||||
List<Long> selectGroupIds(@Param("fileMd5") String fileMd5, @Param("sheetName") String sheetName);
|
||||
|
||||
List<PayPostcodeGroup> selectGroupsByTokens(@Param("tokens") List<String> tokens);
|
||||
|
||||
List<PayPostcodeLine> selectRegionKeys(@Param("groupIds") List<Long> groupIds);
|
||||
|
||||
List<PayPostcodeLine> selectLinesByGroups(@Param("groupIds") List<Long> groupIds);
|
||||
|
||||
int deleteLines(@Param("groupIds") List<Long> groupIds);
|
||||
|
||||
int deleteGroups(@Param("groupIds") List<Long> groupIds);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.PayExtraLine;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
import com.bms.price.domain.PaySurcharge;
|
||||
import com.bms.price.domain.PayWeightBand;
|
||||
|
||||
/**
|
||||
* 应付价卡产品、重量段和附加费
|
||||
*/
|
||||
public interface PayProductMapper
|
||||
{
|
||||
List<PayProduct> selectPayProductList(PayProduct query);
|
||||
|
||||
PayProduct selectPayProductById(Long productId);
|
||||
|
||||
Long selectExistingId(@Param("fileMd5") String fileMd5, @Param("sheetName") String sheetName,
|
||||
@Param("productName") String productName);
|
||||
|
||||
Long selectTemplateId(@Param("dictValue") String dictValue);
|
||||
|
||||
List<PayProduct> selectTemplateOptions();
|
||||
|
||||
Integer selectMaxVersion(@Param("originId") Long originId);
|
||||
|
||||
int insertPayProduct(PayProduct product);
|
||||
|
||||
int updatePayProduct(PayProduct product);
|
||||
|
||||
int insertWeightBands(@Param("list") List<PayWeightBand> list);
|
||||
|
||||
int insertSurcharges(@Param("list") List<PaySurcharge> list);
|
||||
|
||||
int insertExtraLines(@Param("list") List<PayExtraLine> list);
|
||||
|
||||
List<PayWeightBand> selectWeightBands(@Param("productIds") List<Long> productIds);
|
||||
|
||||
List<PaySurcharge> selectSurcharges(@Param("productIds") List<Long> productIds);
|
||||
|
||||
List<PayExtraLine> selectExtraLines(@Param("productIds") List<Long> productIds);
|
||||
|
||||
int deleteWeightBands(@Param("productIds") List<Long> productIds);
|
||||
|
||||
int deleteSurcharges(@Param("productIds") List<Long> productIds);
|
||||
|
||||
int deleteExtraLines(@Param("productIds") List<Long> productIds);
|
||||
|
||||
int deletePayProducts(@Param("productIds") List<Long> productIds);
|
||||
|
||||
List<Long> selectFamilyIds(@Param("rootId") Long rootId);
|
||||
|
||||
Long selectEffectiveVersionId(@Param("stableId") Long stableId, @Param("bizDate") Date bizDate);
|
||||
|
||||
PayProduct selectInactiveCovering(@Param("stableId") Long stableId, @Param("bizDate") Date bizDate);
|
||||
|
||||
Long selectLatestVersionId(@Param("stableId") Long stableId);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.PayProductTerm;
|
||||
|
||||
/**
|
||||
* 应付产品价格有效期
|
||||
*/
|
||||
public interface PayProductTermMapper
|
||||
{
|
||||
List<PayProductTerm> selectByDict(@Param("dictValue") String dictValue);
|
||||
|
||||
PayProductTerm selectById(Long termId);
|
||||
|
||||
PayProductTerm selectByProductId(Long productId);
|
||||
|
||||
int insertTerm(PayProductTerm term);
|
||||
|
||||
int updatePeriod(PayProductTerm term);
|
||||
|
||||
int updateStatus(PayProductTerm term);
|
||||
|
||||
/** 新版本生效时,把同一产品仍生效的旧版本关掉,结束日收到新版本开始的前一天 */
|
||||
int closeOpenTerms(@Param("dictValue") String dictValue, @Param("endDate") Date endDate, @Param("updateBy") String updateBy);
|
||||
|
||||
int deleteByProductIds(@Param("productIds") List<Long> productIds);
|
||||
|
||||
int updateProductEffectiveDate(@Param("productId") Long productId, @Param("effectiveDate") Date effectiveDate);
|
||||
}
|
||||
@@ -134,4 +134,7 @@ public interface PrcOrderBillMapper
|
||||
@Param("surchargeAmount") BigDecimal surchargeAmount,
|
||||
@Param("incomeTotal") BigDecimal incomeTotal,
|
||||
@Param("payTotal") BigDecimal payTotal);
|
||||
|
||||
/** 只更新应付合计,不改应收。 */
|
||||
int updatePayTotal(@Param("billId") String billId, @Param("payTotal") BigDecimal payTotal);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,444 @@
|
||||
package com.bms.price.pay;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import com.bms.price.domain.PayCostLine;
|
||||
import com.bms.price.domain.PayOrderCostResult;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
import com.bms.price.domain.PaySurcharge;
|
||||
import com.bms.price.domain.PayWeightBand;
|
||||
|
||||
/**
|
||||
* 用渠道上的应付产品,按订单重量、尺寸、邮编和是否退件计算应付。
|
||||
* 重量段价格默认是该段的一票价格;单位写明每公斤时才乘重量。
|
||||
*/
|
||||
public final class PayCostCalculator
|
||||
{
|
||||
private static final ZoneId SHANGHAI = ZoneId.of("GMT+8");
|
||||
|
||||
private PayCostCalculator()
|
||||
{
|
||||
}
|
||||
|
||||
public static PayOrderCostResult calculate(PayProduct product, BigDecimal weight, BigDecimal length, BigDecimal width,
|
||||
BigDecimal height, String postcode, Date bizDate, Integer orderStatus)
|
||||
{
|
||||
return calculate(product, weight, length, width, height, postcode, null, bizDate, orderStatus);
|
||||
}
|
||||
|
||||
public static PayOrderCostResult calculate(PayProduct product, BigDecimal weight, BigDecimal length, BigDecimal width,
|
||||
BigDecimal height, String postcode, String zoneName, Date bizDate, Integer orderStatus)
|
||||
{
|
||||
PayOrderCostResult result = new PayOrderCostResult();
|
||||
result.setFees(new ArrayList<>());
|
||||
result.setSkipped(new ArrayList<>());
|
||||
if (product == null)
|
||||
{
|
||||
result.setMessage("没有应付产品");
|
||||
return result;
|
||||
}
|
||||
result.setProductId(product.getProductId());
|
||||
result.setProductName(product.getProductName());
|
||||
if (weight == null || weight.compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
result.setMessage("订单没有计费重");
|
||||
return result;
|
||||
}
|
||||
List<PayWeightBand> bands = forZone(usableBands(product.getWeightBands()), zoneName);
|
||||
if (zoneName != null && !zoneName.isBlank() && bands.isEmpty())
|
||||
{
|
||||
result.setMessage("邮编对应的地区「" + zoneName + "」不在价卡重量段里");
|
||||
return result;
|
||||
}
|
||||
Set<String> zones = zones(bands);
|
||||
if (zones.size() > 1)
|
||||
{
|
||||
result.setMessage("产品「" + product.getProductName() + "」有多个地区(" + String.join("、", zones)
|
||||
+ "),邮编没有对上其中的地区,无法确定运费");
|
||||
return result;
|
||||
}
|
||||
PayWeightBand band = matchBand(bands, weight);
|
||||
if (band == null)
|
||||
{
|
||||
result.setMessage(outOfRange(product, bands, weight));
|
||||
return result;
|
||||
}
|
||||
String currency = currencyOf(product, band);
|
||||
BigDecimal freight = bandAmount(band, weight);
|
||||
String freightRemark = band.getWeightLabel() + " " + plain(band.getPrice()) + " " + blank(band.getPriceUnit());
|
||||
if (zoneName != null && !zoneName.isBlank())
|
||||
{
|
||||
freightRemark = freightRemark + ",地区 " + zoneName.replace('\n', ' ');
|
||||
}
|
||||
PayCostLine freightLine = line("BASIC_FREIGHT", "基础运费", 0L, freight, currency, freightRemark);
|
||||
result.getFees().add(freightLine);
|
||||
result.setCurrency(currency);
|
||||
addSurcharges(product, weight, length, width, height, postcode, zoneName, bizDate, orderStatus, freight, currency, result);
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (PayCostLine fee : result.getFees())
|
||||
{
|
||||
total = total.add(fee.getAmount());
|
||||
}
|
||||
result.setPayTotal(total.setScale(2, RoundingMode.HALF_UP));
|
||||
result.setMessage(summary(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<PayWeightBand> forZone(List<PayWeightBand> bands, String zoneName)
|
||||
{
|
||||
if (zoneName == null || zoneName.isBlank())
|
||||
{
|
||||
return bands;
|
||||
}
|
||||
List<PayWeightBand> kept = new ArrayList<>();
|
||||
for (PayWeightBand band : bands)
|
||||
{
|
||||
if (PayZoneMatcher.sameZone(band.getZoneName(), zoneName))
|
||||
{
|
||||
kept.add(band);
|
||||
}
|
||||
}
|
||||
return kept;
|
||||
}
|
||||
|
||||
private static void addSurcharges(PayProduct product, BigDecimal weight, BigDecimal length, BigDecimal width,
|
||||
BigDecimal height, String postcode, String zoneName, Date bizDate, Integer orderStatus, BigDecimal freight, String currency,
|
||||
PayOrderCostResult result)
|
||||
{
|
||||
if (product.getSurcharges() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<PayCostLine> sized = new ArrayList<>();
|
||||
for (PaySurcharge fee : product.getSurcharges())
|
||||
{
|
||||
if (fee == null || !inForce(fee, bizDate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (zoneName != null && !zoneName.isBlank() && fee.getZoneName() != null && !fee.getZoneName().isBlank()
|
||||
&& !PayZoneMatcher.sameZone(fee.getZoneName(), zoneName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String rule = fee.getCalcRule() == null || fee.getCalcRule().isBlank() ? PaySurchargeRules.MANUAL : fee.getCalcRule();
|
||||
String name = blank(fee.getFeeName());
|
||||
if (PaySurchargeRules.POSTCODE.equals(rule))
|
||||
{
|
||||
if (postcode == null || postcode.isBlank())
|
||||
{
|
||||
result.getSkipped().add(name + ":订单没有邮编");
|
||||
}
|
||||
else if (PaySurchargeRules.matchesPostcode(fee.getCalcParam(), postcode))
|
||||
{
|
||||
addFlat(result.getFees(), fee, currency, "邮编 " + postcode.trim());
|
||||
}
|
||||
}
|
||||
else if (PaySurchargeRules.WEIGHT.equals(rule))
|
||||
{
|
||||
if (PaySurchargeRules.matchesWeight(fee.getCalcParam(), weight))
|
||||
{
|
||||
addFlat(result.getFees(), fee, currency, "计费重超过 " + fee.getCalcParam() + "kg");
|
||||
}
|
||||
}
|
||||
else if (PaySurchargeRules.SIZE.equals(rule))
|
||||
{
|
||||
if (length == null || width == null || height == null)
|
||||
{
|
||||
result.getSkipped().add(name + ":订单没有长宽高");
|
||||
}
|
||||
else if (PaySurchargeRules.matchesSize(fee.getCalcParam(), length, width, height))
|
||||
{
|
||||
PayCostLine matched = matchedFee(fee, freight, currency, "尺寸命中 " + fee.getCalcParam());
|
||||
if (matched != null)
|
||||
{
|
||||
sized.add(matched);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (PaySurchargeRules.RETURN.equals(rule))
|
||||
{
|
||||
if (orderStatus != null && orderStatus == 9)
|
||||
{
|
||||
addFlat(result.getFees(), fee, currency, "订单已退件");
|
||||
}
|
||||
}
|
||||
else if (PaySurchargeRules.ALWAYS.equals(rule))
|
||||
{
|
||||
PayCostLine matched = matchedFee(fee, freight, currency, blank(fee.getChargeText()));
|
||||
if (matched != null)
|
||||
{
|
||||
result.getFees().add(matched);
|
||||
}
|
||||
}
|
||||
else if (!name.isEmpty() && !name.contains("报价说明"))
|
||||
{
|
||||
result.getSkipped().add(name + ":价卡没有可计算的条件,需人工确认");
|
||||
}
|
||||
}
|
||||
result.getFees().addAll(keepHighestByName(sized));
|
||||
}
|
||||
|
||||
/** 同一费用名的尺寸档同时命中时,只收较高的一档,例如 70% 覆盖 35%。 */
|
||||
private static List<PayCostLine> keepHighestByName(List<PayCostLine> lines)
|
||||
{
|
||||
List<PayCostLine> kept = new ArrayList<>();
|
||||
for (PayCostLine line : lines)
|
||||
{
|
||||
PayCostLine same = null;
|
||||
for (PayCostLine exists : kept)
|
||||
{
|
||||
if (exists.getFeeName().equals(line.getFeeName()))
|
||||
{
|
||||
same = exists;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (same == null)
|
||||
{
|
||||
kept.add(line);
|
||||
}
|
||||
else if (line.getAmount().compareTo(same.getAmount()) > 0)
|
||||
{
|
||||
kept.set(kept.indexOf(same), line);
|
||||
}
|
||||
}
|
||||
return kept;
|
||||
}
|
||||
|
||||
private static void addFlat(List<PayCostLine> fees, PaySurcharge fee, String currency, String remark)
|
||||
{
|
||||
PayCostLine line = matchedFee(fee, null, currency, remark);
|
||||
if (line != null && !percent(fee))
|
||||
{
|
||||
fees.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
private static PayCostLine matchedFee(PaySurcharge fee, BigDecimal freight, String currency, String remark)
|
||||
{
|
||||
if (fee.getAmount() == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
BigDecimal amount = fee.getAmount();
|
||||
if (percent(fee))
|
||||
{
|
||||
if (freight == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
amount = freight.multiply(fee.getAmount()).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = amount.setScale(2, RoundingMode.HALF_UP);
|
||||
String feeCurrency = normalize(fee.getCurrency());
|
||||
if (!feeCurrency.isEmpty() && !feeCurrency.equals(currency))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
long scId = fee.getSurchargeId() == null ? 0L : fee.getSurchargeId();
|
||||
return line("SURCHARGE", blank(fee.getFeeName()), scId, amount, currency, remark);
|
||||
}
|
||||
|
||||
private static boolean percent(PaySurcharge fee)
|
||||
{
|
||||
String charge = fee.getChargeText() == null ? "" : fee.getChargeText();
|
||||
return charge.contains("%") || "%".equals(fee.getCurrency());
|
||||
}
|
||||
|
||||
private static boolean inForce(PaySurcharge fee, Date bizDate)
|
||||
{
|
||||
if (bizDate == null || (fee.getEffectiveStart() == null && fee.getEffectiveEnd() == null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
LocalDate day = toLocal(bizDate);
|
||||
if (fee.getEffectiveStart() != null && day.isBefore(toLocal(fee.getEffectiveStart())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (fee.getEffectiveEnd() != null && day.isAfter(toLocal(fee.getEffectiveEnd())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<PayWeightBand> usableBands(List<PayWeightBand> bands)
|
||||
{
|
||||
List<PayWeightBand> usable = new ArrayList<>();
|
||||
if (bands == null)
|
||||
{
|
||||
return usable;
|
||||
}
|
||||
for (PayWeightBand band : bands)
|
||||
{
|
||||
if (band != null && band.getPrice() != null && band.getWeightMin() != null && band.getWeightMax() != null)
|
||||
{
|
||||
usable.add(band);
|
||||
}
|
||||
}
|
||||
usable.sort((left, right) -> left.getWeightMin().compareTo(right.getWeightMin()));
|
||||
return usable;
|
||||
}
|
||||
|
||||
private static Set<String> zones(List<PayWeightBand> bands)
|
||||
{
|
||||
Set<String> zones = new LinkedHashSet<>();
|
||||
for (PayWeightBand band : bands)
|
||||
{
|
||||
if (band.getZoneName() != null && !band.getZoneName().isBlank())
|
||||
{
|
||||
zones.add(band.getZoneName().trim());
|
||||
}
|
||||
}
|
||||
return zones;
|
||||
}
|
||||
|
||||
private static PayWeightBand matchBand(List<PayWeightBand> bands, BigDecimal weight)
|
||||
{
|
||||
for (PayWeightBand band : bands)
|
||||
{
|
||||
if (weight.compareTo(band.getWeightMin()) >= 0 && weight.compareTo(band.getWeightMax()) <= 0)
|
||||
{
|
||||
return band;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < bands.size() - 1; i++)
|
||||
{
|
||||
PayWeightBand current = bands.get(i);
|
||||
PayWeightBand next = bands.get(i + 1);
|
||||
BigDecimal gap = next.getWeightMin().subtract(current.getWeightMax());
|
||||
if (weight.compareTo(current.getWeightMax()) > 0 && weight.compareTo(next.getWeightMin()) < 0
|
||||
&& gap.compareTo(new BigDecimal("0.01")) <= 0)
|
||||
{
|
||||
return next;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String outOfRange(PayProduct product, List<PayWeightBand> bands, BigDecimal weight)
|
||||
{
|
||||
String name = blank(product.getProductName());
|
||||
if (bands.isEmpty())
|
||||
{
|
||||
return "产品「" + name + "」没有可匹配的重量段";
|
||||
}
|
||||
BigDecimal max = bands.get(bands.size() - 1).getWeightMax();
|
||||
if (weight.compareTo(max) > 0)
|
||||
{
|
||||
return "计费重 " + plain(weight) + "kg 超出「" + name + "」的重量段,最大 " + plain(max) + "kg";
|
||||
}
|
||||
return "计费重 " + plain(weight) + "kg 没有落在「" + name + "」的重量段里";
|
||||
}
|
||||
|
||||
private static BigDecimal bandAmount(PayWeightBand band, BigDecimal weight)
|
||||
{
|
||||
BigDecimal price = band.getPrice();
|
||||
if (perKilogram(band))
|
||||
{
|
||||
return price.multiply(weight).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
return price.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private static boolean perKilogram(PayWeightBand band)
|
||||
{
|
||||
String blob = (blank(band.getPriceUnit()) + " " + blank(band.getChargeName())).toLowerCase(Locale.ROOT);
|
||||
return blob.contains("/kg") || blob.contains("每公斤") || blob.contains("每千克") || blob.contains("/公斤");
|
||||
}
|
||||
|
||||
private static String currencyOf(PayProduct product, PayWeightBand band)
|
||||
{
|
||||
String unit = blank(band.getPriceUnit()).toUpperCase(Locale.ROOT);
|
||||
if (unit.contains("GBP") || unit.contains("英镑"))
|
||||
{
|
||||
return "GBP";
|
||||
}
|
||||
if (unit.contains("EUR") || unit.contains("欧元"))
|
||||
{
|
||||
return "EUR";
|
||||
}
|
||||
if (unit.contains("USD") || unit.contains("美元"))
|
||||
{
|
||||
return "USD";
|
||||
}
|
||||
if (unit.contains("CNY") || unit.contains("RMB") || unit.contains("人民币"))
|
||||
{
|
||||
return "CNY";
|
||||
}
|
||||
String currency = blank(product.getCurrency());
|
||||
return currency.isEmpty() ? "CNY" : currency;
|
||||
}
|
||||
|
||||
private static PayCostLine line(String feeType, String feeName, Long scId, BigDecimal amount, String currency, String remark)
|
||||
{
|
||||
PayCostLine line = new PayCostLine();
|
||||
line.setFeeType(feeType);
|
||||
line.setFeeName(feeName);
|
||||
line.setScId(scId);
|
||||
line.setAmount(amount.setScale(2, RoundingMode.HALF_UP));
|
||||
line.setCurrency(currency);
|
||||
line.setRemark(remark == null ? "" : remark.trim());
|
||||
return line;
|
||||
}
|
||||
|
||||
private static String summary(PayOrderCostResult result)
|
||||
{
|
||||
String text = "应付 " + plain(result.getPayTotal()) + " " + result.getCurrency();
|
||||
if (result.getSkipped() != null && !result.getSkipped().isEmpty())
|
||||
{
|
||||
text = text + ",另有 " + result.getSkipped().size() + " 项附加费未自动计算";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private static LocalDate toLocal(Date date)
|
||||
{
|
||||
if (date instanceof java.sql.Date sql)
|
||||
{
|
||||
return sql.toLocalDate();
|
||||
}
|
||||
return date.toInstant().atZone(SHANGHAI).toLocalDate();
|
||||
}
|
||||
|
||||
private static String plain(BigDecimal value)
|
||||
{
|
||||
return value == null ? "" : value.stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
private static String blank(String value)
|
||||
{
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static String normalize(String currency)
|
||||
{
|
||||
if (currency == null || currency.isBlank() || "%".equals(currency.trim()))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
String value = currency.trim().toUpperCase(Locale.ROOT);
|
||||
if ("英镑".equals(currency))
|
||||
{
|
||||
return "GBP";
|
||||
}
|
||||
if ("欧元".equals(currency))
|
||||
{
|
||||
return "EUR";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.bms.price.pay;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import com.bms.price.domain.PayExtraLine;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
import com.bms.price.domain.PaySurcharge;
|
||||
import com.bms.price.domain.PayWeightBand;
|
||||
|
||||
/**
|
||||
* 把已保存的应付配置导出成产品、重量段、附加费、其他配置四张表。
|
||||
*/
|
||||
public final class PayDetailExporter
|
||||
{
|
||||
private static final int MAX_ROWS = 50000;
|
||||
|
||||
private PayDetailExporter()
|
||||
{
|
||||
}
|
||||
|
||||
public static void write(OutputStream output, List<PayProduct> products) throws IOException
|
||||
{
|
||||
try (XSSFWorkbook workbook = new XSSFWorkbook())
|
||||
{
|
||||
Sheet productSheet = workbook.createSheet("产品");
|
||||
header(productSheet, "产品名称", "价卡归属", "国家/口岸", "生效日期", "报价工作表", "关联分区表", "币种",
|
||||
"识别结果", "重量段数", "附加费数", "其他配置数", "来源文件", "服务说明");
|
||||
Sheet weightSheet = workbook.createSheet("重量段");
|
||||
header(weightSheet, "产品名称", "报价工作表", "区块", "国家", "国家代码", "分区", "重量段", "下限KG", "上限KG",
|
||||
"体积", "费用项", "单价", "单位");
|
||||
Sheet feeSheet = workbook.createSheet("附加费");
|
||||
header(feeSheet, "产品名称", "报价工作表", "区块", "费用名称", "分区", "收费标准", "金额", "币种", "收取时点",
|
||||
"生效开始", "生效结束", "时效原文", "费用说明");
|
||||
Sheet extraSheet = workbook.createSheet("其他配置");
|
||||
header(extraSheet, "产品名称", "报价工作表", "类型", "配置内容");
|
||||
int weightRows = 1;
|
||||
int feeRows = 1;
|
||||
int extraRows = 1;
|
||||
if (products != null)
|
||||
{
|
||||
int rowIndex = 1;
|
||||
for (PayProduct product : products)
|
||||
{
|
||||
Row row = productSheet.createRow(rowIndex++);
|
||||
cell(row, 0, product.getProductName());
|
||||
cell(row, 1, product.getSupplierName());
|
||||
cell(row, 2, product.getCountryScope());
|
||||
cell(row, 3, day(product.getEffectiveDate()));
|
||||
cell(row, 4, product.getSheetName());
|
||||
cell(row, 5, product.getRelatedSheet());
|
||||
cell(row, 6, product.getCurrency());
|
||||
cell(row, 7, product.getParseStatus());
|
||||
cell(row, 8, String.valueOf(product.getWeightCount()));
|
||||
cell(row, 9, String.valueOf(product.getSurchargeCount()));
|
||||
cell(row, 10, String.valueOf(product.getExtraCount()));
|
||||
cell(row, 11, product.getSourceFile());
|
||||
cell(row, 12, product.getServiceNote());
|
||||
weightRows = writeWeights(weightSheet, weightRows, product);
|
||||
feeRows = writeFees(feeSheet, feeRows, product);
|
||||
extraRows = writeExtras(extraSheet, extraRows, product);
|
||||
}
|
||||
}
|
||||
workbook.write(output);
|
||||
}
|
||||
}
|
||||
|
||||
private static int writeWeights(Sheet sheet, int rowIndex, PayProduct product)
|
||||
{
|
||||
if (product.getWeightBands() == null)
|
||||
{
|
||||
return rowIndex;
|
||||
}
|
||||
for (PayWeightBand band : product.getWeightBands())
|
||||
{
|
||||
if (rowIndex >= MAX_ROWS)
|
||||
{
|
||||
return rowIndex;
|
||||
}
|
||||
Row row = sheet.createRow(rowIndex++);
|
||||
cell(row, 0, product.getProductName());
|
||||
cell(row, 1, product.getSheetName());
|
||||
cell(row, 2, band.getSectionName());
|
||||
cell(row, 3, band.getCountryName());
|
||||
cell(row, 4, band.getCountryCode());
|
||||
cell(row, 5, band.getZoneName());
|
||||
cell(row, 6, band.getWeightLabel());
|
||||
cell(row, 7, plain(band.getWeightMin()));
|
||||
cell(row, 8, plain(band.getWeightMax()));
|
||||
cell(row, 9, band.getVolumeText());
|
||||
cell(row, 10, band.getChargeName());
|
||||
cell(row, 11, plain(band.getPrice()));
|
||||
cell(row, 12, band.getPriceUnit());
|
||||
}
|
||||
return rowIndex;
|
||||
}
|
||||
|
||||
private static int writeFees(Sheet sheet, int rowIndex, PayProduct product)
|
||||
{
|
||||
if (product.getSurcharges() == null)
|
||||
{
|
||||
return rowIndex;
|
||||
}
|
||||
for (PaySurcharge fee : product.getSurcharges())
|
||||
{
|
||||
if (rowIndex >= MAX_ROWS)
|
||||
{
|
||||
return rowIndex;
|
||||
}
|
||||
Row row = sheet.createRow(rowIndex++);
|
||||
cell(row, 0, product.getProductName());
|
||||
cell(row, 1, product.getSheetName());
|
||||
cell(row, 2, fee.getSectionName());
|
||||
cell(row, 3, fee.getFeeName());
|
||||
cell(row, 4, fee.getZoneName());
|
||||
cell(row, 5, fee.getChargeText());
|
||||
cell(row, 6, plain(fee.getAmount()));
|
||||
cell(row, 7, fee.getCurrency());
|
||||
cell(row, 8, fee.getChargeTiming());
|
||||
cell(row, 9, day(fee.getEffectiveStart()));
|
||||
cell(row, 10, day(fee.getEffectiveEnd()));
|
||||
cell(row, 11, fee.getPeriodText());
|
||||
cell(row, 12, fee.getFeeDesc());
|
||||
}
|
||||
return rowIndex;
|
||||
}
|
||||
|
||||
private static int writeExtras(Sheet sheet, int rowIndex, PayProduct product)
|
||||
{
|
||||
if (product.getExtraLines() == null)
|
||||
{
|
||||
return rowIndex;
|
||||
}
|
||||
for (PayExtraLine line : product.getExtraLines())
|
||||
{
|
||||
if (rowIndex >= MAX_ROWS)
|
||||
{
|
||||
return rowIndex;
|
||||
}
|
||||
Row row = sheet.createRow(rowIndex++);
|
||||
cell(row, 0, product.getProductName());
|
||||
cell(row, 1, product.getSheetName());
|
||||
cell(row, 2, line.getLineType());
|
||||
cell(row, 3, line.getLineText());
|
||||
}
|
||||
return rowIndex;
|
||||
}
|
||||
|
||||
private static void header(Sheet sheet, String... titles)
|
||||
{
|
||||
Row row = sheet.createRow(0);
|
||||
for (int i = 0; i < titles.length; i++)
|
||||
{
|
||||
row.createCell(i).setCellValue(titles[i]);
|
||||
sheet.setColumnWidth(i, 18 * 256);
|
||||
}
|
||||
}
|
||||
|
||||
private static void cell(Row row, int col, String value)
|
||||
{
|
||||
row.createCell(col).setCellValue(value == null ? "" : value);
|
||||
}
|
||||
|
||||
private static String plain(java.math.BigDecimal value)
|
||||
{
|
||||
return value == null ? "" : value.stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
private static String day(Date value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return new SimpleDateFormat("yyyy-MM-dd").format(value);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,355 @@
|
||||
package com.bms.price.pay;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import com.bms.price.domain.PaySurcharge;
|
||||
|
||||
/**
|
||||
* 把附加费说明收成可计算的条件。认不出的保持人工,避免把签名、重复单号当成每票必收。
|
||||
*/
|
||||
public final class PaySurchargeRules
|
||||
{
|
||||
public static final String ALWAYS = "ALWAYS";
|
||||
|
||||
public static final String POSTCODE = "POSTCODE";
|
||||
|
||||
public static final String WEIGHT = "WEIGHT";
|
||||
|
||||
public static final String SIZE = "SIZE";
|
||||
|
||||
public static final String RETURN = "RETURN";
|
||||
|
||||
public static final String MANUAL = "MANUAL";
|
||||
|
||||
private static final Pattern RANGE = Pattern.compile("([A-Z]{1,2})(\\d{1,2})\\s*-\\s*(\\d{1,2})");
|
||||
|
||||
private static final Pattern DISTRICT = Pattern.compile("([A-Z]{1,2})(\\d{1,2})");
|
||||
|
||||
private static final Pattern AREA = Pattern.compile("^[A-Z]{1,2}$");
|
||||
|
||||
private static final Pattern UK_FULL = Pattern.compile("^([A-Z]{1,2})(\\d{1,2})[A-Z]?\\d[A-Z]{2}$");
|
||||
|
||||
private static final Pattern UK_OUTWARD = Pattern.compile("^([A-Z]{1,2})(\\d{1,2})$");
|
||||
|
||||
private static final Pattern BOX = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*\\*\\s*(\\d+(?:\\.\\d+)?)\\s*\\*\\s*(\\d+(?:\\.\\d+)?)");
|
||||
|
||||
private static final Pattern VOLUME = Pattern.compile("(?i)(\\d+(?:\\.\\d+)?)\\s*(?:m3|m³|立方米)");
|
||||
|
||||
private static final Pattern CM = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*(?:cm|厘米)");
|
||||
|
||||
private static final Pattern KG = Pattern.compile("(?i)(\\d+(?:\\.\\d+)?)\\s*(?:kg|公斤|千克)");
|
||||
|
||||
private PaySurchargeRules()
|
||||
{
|
||||
}
|
||||
|
||||
public static void apply(PaySurcharge fee)
|
||||
{
|
||||
if (fee == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
String name = fee.getFeeName() == null ? "" : fee.getFeeName();
|
||||
String desc = fee.getFeeDesc() == null ? "" : fee.getFeeDesc();
|
||||
String charge = fee.getChargeText() == null ? "" : fee.getChargeText();
|
||||
String blob = name + " " + desc + " " + charge;
|
||||
if (name.contains("报价说明"))
|
||||
{
|
||||
fee.setCalcRule(MANUAL);
|
||||
return;
|
||||
}
|
||||
if (name.contains("退件"))
|
||||
{
|
||||
fee.setCalcRule(RETURN);
|
||||
return;
|
||||
}
|
||||
if (name.contains("签名") || name.contains("重复") || name.contains("未预报") || name.contains("重新贴"))
|
||||
{
|
||||
fee.setCalcRule(MANUAL);
|
||||
return;
|
||||
}
|
||||
String postcodes = postcodeParam(desc);
|
||||
if (name.contains("偏远") && !postcodes.isEmpty())
|
||||
{
|
||||
fee.setCalcRule(POSTCODE);
|
||||
fee.setCalcParam(postcodes);
|
||||
return;
|
||||
}
|
||||
Matcher kg = KG.matcher(blob);
|
||||
if ((name.contains("超重") || blob.contains("重量>") || blob.toLowerCase(Locale.ROOT).contains("weight")) && kg.find())
|
||||
{
|
||||
fee.setCalcRule(WEIGHT);
|
||||
fee.setCalcParam(kg.group(1));
|
||||
return;
|
||||
}
|
||||
String size = sizeParam(blob);
|
||||
if (!size.isEmpty() && (name.contains("超大") || name.contains("超尺寸") || name.contains("特大") || blob.contains("厘米") || blob.toLowerCase(Locale.ROOT).contains("cm")))
|
||||
{
|
||||
fee.setCalcRule(SIZE);
|
||||
fee.setCalcParam(size);
|
||||
return;
|
||||
}
|
||||
if (blob.contains("固定收取") || "固定收取".equals(fee.getChargeTiming()))
|
||||
{
|
||||
fee.setCalcRule(ALWAYS);
|
||||
return;
|
||||
}
|
||||
fee.setCalcRule(MANUAL);
|
||||
}
|
||||
|
||||
public static boolean matchesPostcode(String param, String postcode)
|
||||
{
|
||||
UkPostcode parsed = parseUk(postcode);
|
||||
if (parsed == null || param == null || param.isBlank())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (String token : param.split(","))
|
||||
{
|
||||
if (tokenMatches(token.trim().toUpperCase(Locale.ROOT), parsed))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean matchesWeight(String param, BigDecimal weight)
|
||||
{
|
||||
if (param == null || param.isBlank() || weight == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
return weight.compareTo(new BigDecimal(param.trim())) > 0;
|
||||
}
|
||||
catch (NumberFormatException ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 长宽高单位厘米。箱规按从大到小比较,体积用立方米,总和是三边相加。 */
|
||||
public static boolean matchesSize(String param, BigDecimal length, BigDecimal width, BigDecimal height)
|
||||
{
|
||||
if (param == null || param.isBlank() || length == null || width == null || height == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
BigDecimal[] sides = desc(length, width, height);
|
||||
boolean sideHit = false;
|
||||
boolean sumHit = false;
|
||||
boolean sideSeen = false;
|
||||
boolean sumSeen = false;
|
||||
String join = "or";
|
||||
for (String part : param.split(";"))
|
||||
{
|
||||
String[] pair = part.split(":", 2);
|
||||
if (pair.length < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String key = pair[0].trim();
|
||||
String value = pair[1].trim();
|
||||
if ("join".equals(key))
|
||||
{
|
||||
join = value;
|
||||
continue;
|
||||
}
|
||||
if ("box".equals(key))
|
||||
{
|
||||
String[] dims = value.split("\\*");
|
||||
if (dims.length == 3 && exceedsBox(sides, decimal(dims[0]), decimal(dims[1]), decimal(dims[2])))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ("volume".equals(key))
|
||||
{
|
||||
BigDecimal cubic = length.multiply(width).multiply(height)
|
||||
.divide(new BigDecimal("1000000"), 6, RoundingMode.HALF_UP);
|
||||
if (cubic.compareTo(decimal(value)) >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ("side".equals(key))
|
||||
{
|
||||
sideSeen = true;
|
||||
sideHit = sides[0].compareTo(decimal(value)) > 0;
|
||||
}
|
||||
else if ("sum".equals(key))
|
||||
{
|
||||
sumSeen = true;
|
||||
sumHit = length.add(width).add(height).compareTo(decimal(value)) > 0;
|
||||
}
|
||||
}
|
||||
if (sideSeen || sumSeen)
|
||||
{
|
||||
if ("and".equals(join))
|
||||
{
|
||||
return (!sideSeen || sideHit) && (!sumSeen || sumHit);
|
||||
}
|
||||
return sideHit || sumHit;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String postcodeParam(String desc)
|
||||
{
|
||||
if (desc == null || desc.isBlank())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
int cut = Math.max(desc.lastIndexOf(';'), desc.lastIndexOf(';'));
|
||||
String source = cut >= 0 ? desc.substring(cut + 1) : desc;
|
||||
String upper = source.toUpperCase(Locale.ROOT).replace(',', ',').replace('、', ',').replace('.', ',');
|
||||
List<String> tokens = new ArrayList<>();
|
||||
for (String raw : upper.split("[,\\s]+"))
|
||||
{
|
||||
String token = raw.trim();
|
||||
if (token.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (RANGE.matcher(token).matches() || DISTRICT.matcher(token).matches() || AREA.matcher(token).matches())
|
||||
{
|
||||
tokens.add(token);
|
||||
}
|
||||
}
|
||||
return String.join(",", tokens);
|
||||
}
|
||||
|
||||
private static String sizeParam(String blob)
|
||||
{
|
||||
StringBuilder param = new StringBuilder();
|
||||
Matcher box = BOX.matcher(blob);
|
||||
if (box.find())
|
||||
{
|
||||
param.append("box:").append(box.group(1)).append('*').append(box.group(2)).append('*').append(box.group(3));
|
||||
}
|
||||
Matcher volume = VOLUME.matcher(blob);
|
||||
if (volume.find())
|
||||
{
|
||||
if (param.length() > 0)
|
||||
{
|
||||
param.append(';');
|
||||
}
|
||||
param.append("volume:").append(volume.group(1));
|
||||
}
|
||||
List<String> cms = new ArrayList<>();
|
||||
Matcher cm = CM.matcher(blob);
|
||||
while (cm.find())
|
||||
{
|
||||
cms.add(cm.group(1));
|
||||
}
|
||||
if (cms.size() >= 1 && (blob.contains("一边") || blob.toLowerCase(Locale.ROOT).contains("length") || blob.contains("最长")))
|
||||
{
|
||||
if (param.length() > 0)
|
||||
{
|
||||
param.append(';');
|
||||
}
|
||||
param.append("side:").append(cms.get(0));
|
||||
if (cms.size() >= 2 && (blob.contains("总和") || blob.toLowerCase(Locale.ROOT).contains("sum")))
|
||||
{
|
||||
param.append(";sum:").append(cms.get(1));
|
||||
param.append(";join:").append(join(blob));
|
||||
}
|
||||
}
|
||||
return param.toString();
|
||||
}
|
||||
|
||||
private static String join(String blob)
|
||||
{
|
||||
if (blob.contains("或") || Pattern.compile("(?i)\\bor\\b").matcher(blob).find())
|
||||
{
|
||||
return "or";
|
||||
}
|
||||
return "and";
|
||||
}
|
||||
|
||||
private static boolean tokenMatches(String token, UkPostcode postcode)
|
||||
{
|
||||
Matcher range = RANGE.matcher(token);
|
||||
if (range.matches())
|
||||
{
|
||||
int low = Integer.parseInt(range.group(2));
|
||||
int high = Integer.parseInt(range.group(3));
|
||||
return postcode.area.equals(range.group(1)) && postcode.district >= low && postcode.district <= high;
|
||||
}
|
||||
Matcher district = DISTRICT.matcher(token);
|
||||
if (district.matches())
|
||||
{
|
||||
return postcode.area.equals(district.group(1)) && postcode.district == Integer.parseInt(district.group(2));
|
||||
}
|
||||
return AREA.matcher(token).matches() && postcode.area.equals(token);
|
||||
}
|
||||
|
||||
private static UkPostcode parseUk(String postcode)
|
||||
{
|
||||
if (postcode == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String compact = postcode.toUpperCase(Locale.ROOT).replaceAll("[^A-Z0-9]", "");
|
||||
Matcher full = UK_FULL.matcher(compact);
|
||||
if (full.matches())
|
||||
{
|
||||
return new UkPostcode(full.group(1), Integer.parseInt(full.group(2)));
|
||||
}
|
||||
Matcher outward = UK_OUTWARD.matcher(compact);
|
||||
if (outward.matches())
|
||||
{
|
||||
return new UkPostcode(outward.group(1), Integer.parseInt(outward.group(2)));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean exceedsBox(BigDecimal[] parcel, BigDecimal a, BigDecimal b, BigDecimal c)
|
||||
{
|
||||
BigDecimal[] limit = desc(a, b, c);
|
||||
return parcel[0].compareTo(limit[0]) > 0 || parcel[1].compareTo(limit[1]) > 0 || parcel[2].compareTo(limit[2]) > 0;
|
||||
}
|
||||
|
||||
private static BigDecimal[] desc(BigDecimal a, BigDecimal b, BigDecimal c)
|
||||
{
|
||||
BigDecimal[] values = new BigDecimal[] { a, b, c };
|
||||
for (int i = 0; i < values.length; i++)
|
||||
{
|
||||
for (int j = i + 1; j < values.length; j++)
|
||||
{
|
||||
if (values[j].compareTo(values[i]) > 0)
|
||||
{
|
||||
BigDecimal swap = values[i];
|
||||
values[i] = values[j];
|
||||
values[j] = swap;
|
||||
}
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private static BigDecimal decimal(String text)
|
||||
{
|
||||
return new BigDecimal(text.trim());
|
||||
}
|
||||
|
||||
private static final class UkPostcode
|
||||
{
|
||||
private final String area;
|
||||
|
||||
private final int district;
|
||||
|
||||
private UkPostcode(String area, int district)
|
||||
{
|
||||
this.area = area;
|
||||
this.district = district;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
package com.bms.price.pay;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 价卡单元格里的重量段、金额和时效原文解析。供导入预览和保存共用。
|
||||
*/
|
||||
public final class PayTexts
|
||||
{
|
||||
private static final Pattern WEIGHT_RANGE = Pattern.compile(
|
||||
"(?i)(\\d+(?:\\.\\d+)?)\\s*[<<]\\s*[wW]\\s*[≤<=<]\\s*(\\d+(?:\\.\\d+)?)");
|
||||
|
||||
private static final Pattern WEIGHT_DASH = Pattern.compile(
|
||||
"(?i)(\\d+(?:\\.\\d+)?)\\s*[-~~]\\s*(\\d+(?:\\.\\d+)?)\\s*(?:kg|公斤)?");
|
||||
|
||||
private static final Pattern FIRST_WEIGHT = Pattern.compile("(?i)首重\\s*(\\d+(?:\\.\\d+)?)\\s*(?:kg|公斤)?");
|
||||
|
||||
private static final Pattern STEP_WEIGHT = Pattern.compile("(?i)每增加\\s*(\\d+(?:\\.\\d+)?)");
|
||||
|
||||
private static final Pattern ONLY_KG = Pattern.compile("(?i)^\\s*(\\d+(?:\\.\\d+)?)\\s*(?:kg|公斤)\\s*$");
|
||||
|
||||
private static final Pattern CN_PERIOD = Pattern.compile(
|
||||
"(20\\d{2})\\s*年\\s*(\\d{1,2})\\s*月\\s*(\\d{1,2})\\s*[日号]?\\s*[至到\\-~~]+\\s*(?:(20\\d{2})\\s*年\\s*)?(\\d{1,2})\\s*月\\s*(\\d{1,2})\\s*[日号]?");
|
||||
|
||||
private static final Pattern CN_MONTH = Pattern.compile(
|
||||
"(20\\d{2})\\s*年\\s*(\\d{1,2})\\s*月\\s*[至到\\-~~]+\\s*(\\d{1,2})\\s*月");
|
||||
|
||||
private static final Pattern ISO_DATE = Pattern.compile("(20\\d{2})[./-](\\d{1,2})[./-](\\d{1,2})");
|
||||
|
||||
/** 20260928-20270117 这种连在一起的日期窗口 */
|
||||
private static final Pattern COMPACT_PERIOD = Pattern.compile("(20\\d{2})(\\d{2})(\\d{2})\\s*-\\s*(20\\d{2})(\\d{2})(\\d{2})");
|
||||
|
||||
private static final Pattern EN_PERIOD = Pattern.compile(
|
||||
"(?i)(January|February|March|April|May|June|July|August|September|October|November|December)"
|
||||
+ "\\s+(\\d{1,2})\\w*\\s+to\\s+"
|
||||
+ "(January|February|March|April|May|June|July|August|September|October|November|December)"
|
||||
+ "\\s+(\\d{1,2})\\w*[,\\s]+(20\\d{2})");
|
||||
|
||||
private static final Pattern MONEY = Pattern.compile(
|
||||
"(?i)(\\d+(?:\\.\\d+)?)\\s*(EUR|GBP|USD|RMB|CNY|欧元|英镑|人民币)?");
|
||||
|
||||
private static final Pattern PERCENT = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*%");
|
||||
|
||||
private PayTexts()
|
||||
{
|
||||
}
|
||||
|
||||
/** 从重量段原文取出上下限。每增加一公斤只保留原文,不编造上下限。 */
|
||||
public static BigDecimal[] weightBounds(String label)
|
||||
{
|
||||
BigDecimal[] bounds = new BigDecimal[] { null, null };
|
||||
if (label == null || label.isBlank())
|
||||
{
|
||||
return bounds;
|
||||
}
|
||||
Matcher range = WEIGHT_RANGE.matcher(label);
|
||||
if (range.find())
|
||||
{
|
||||
bounds[0] = decimal(range.group(1));
|
||||
bounds[1] = decimal(range.group(2));
|
||||
return bounds;
|
||||
}
|
||||
Matcher dash = WEIGHT_DASH.matcher(label);
|
||||
if (dash.find())
|
||||
{
|
||||
bounds[0] = decimal(dash.group(1));
|
||||
bounds[1] = decimal(dash.group(2));
|
||||
return bounds;
|
||||
}
|
||||
Matcher first = FIRST_WEIGHT.matcher(label);
|
||||
if (first.find())
|
||||
{
|
||||
bounds[0] = BigDecimal.ZERO;
|
||||
bounds[1] = decimal(first.group(1));
|
||||
return bounds;
|
||||
}
|
||||
if (STEP_WEIGHT.matcher(label).find())
|
||||
{
|
||||
return bounds;
|
||||
}
|
||||
Matcher only = ONLY_KG.matcher(label.trim());
|
||||
if (only.find())
|
||||
{
|
||||
bounds[0] = BigDecimal.ZERO;
|
||||
bounds[1] = decimal(only.group(1));
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public static boolean looksLikeWeight(String text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return WEIGHT_RANGE.matcher(text).find()
|
||||
|| WEIGHT_DASH.matcher(text).find()
|
||||
|| FIRST_WEIGHT.matcher(text).find()
|
||||
|| STEP_WEIGHT.matcher(text).find()
|
||||
|| ONLY_KG.matcher(text.trim()).find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从说明里取第一段时效。defaultYear 用于只有月日、没有年份的句子。
|
||||
*/
|
||||
public static Period period(String text, int defaultYear)
|
||||
{
|
||||
if (text == null || text.isBlank())
|
||||
{
|
||||
return Period.NONE;
|
||||
}
|
||||
Matcher full = CN_PERIOD.matcher(text);
|
||||
if (full.find())
|
||||
{
|
||||
int y1 = Integer.parseInt(full.group(1));
|
||||
int y2 = full.group(4) == null ? y1 : Integer.parseInt(full.group(4));
|
||||
LocalDate start = date(y1, full.group(2), full.group(3));
|
||||
LocalDate end = date(y2, full.group(5), full.group(6));
|
||||
return new Period(start, end, full.group());
|
||||
}
|
||||
Matcher month = CN_MONTH.matcher(text);
|
||||
if (month.find())
|
||||
{
|
||||
int year = Integer.parseInt(month.group(1));
|
||||
int m1 = Integer.parseInt(month.group(2));
|
||||
int m2 = Integer.parseInt(month.group(3));
|
||||
LocalDate start = LocalDate.of(year, m1, 1);
|
||||
LocalDate end = YearMonth.of(year, m2).atEndOfMonth();
|
||||
return new Period(start, end, month.group());
|
||||
}
|
||||
Matcher english = EN_PERIOD.matcher(text);
|
||||
if (english.find())
|
||||
{
|
||||
int year = Integer.parseInt(english.group(5));
|
||||
LocalDate start = date(year, monthNumber(english.group(1)), english.group(2));
|
||||
LocalDate end = date(year, monthNumber(english.group(3)), english.group(4));
|
||||
return new Period(start, end, english.group());
|
||||
}
|
||||
Matcher compact = COMPACT_PERIOD.matcher(text);
|
||||
if (compact.find())
|
||||
{
|
||||
LocalDate start = date(compact.group(1), compact.group(2), compact.group(3));
|
||||
LocalDate end = date(compact.group(4), compact.group(5), compact.group(6));
|
||||
return new Period(start, end, compact.group());
|
||||
}
|
||||
Matcher iso = ISO_DATE.matcher(text);
|
||||
if (iso.find())
|
||||
{
|
||||
LocalDate day = date(iso.group(1), iso.group(2), iso.group(3));
|
||||
return new Period(day, day, iso.group());
|
||||
}
|
||||
return Period.NONE;
|
||||
}
|
||||
|
||||
/** 去掉时效句子后再取金额,避免把 2026 当成费用。 */
|
||||
public static Money money(String text, Period period)
|
||||
{
|
||||
if (text == null || text.isBlank())
|
||||
{
|
||||
return Money.NONE;
|
||||
}
|
||||
String source = text;
|
||||
if (period != null && period.text != null)
|
||||
{
|
||||
source = source.replace(period.text, " ");
|
||||
}
|
||||
Matcher percent = PERCENT.matcher(source);
|
||||
if (percent.find() && !source.matches(".*(?i)(EUR|GBP|USD|RMB|CNY|欧元|英镑).*"))
|
||||
{
|
||||
return new Money(decimal(percent.group(1)), "", "%");
|
||||
}
|
||||
Matcher matcher = MONEY.matcher(source);
|
||||
Money found = Money.NONE;
|
||||
while (matcher.find())
|
||||
{
|
||||
String raw = matcher.group(1);
|
||||
if (raw.startsWith("20") && raw.length() == 4)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String currency = currency(matcher.group(2));
|
||||
String unit = unit(source);
|
||||
found = new Money(decimal(raw), currency, unit);
|
||||
if (!currency.isEmpty())
|
||||
{
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
public static String timing(String text, Period period)
|
||||
{
|
||||
String value = text == null ? "" : text;
|
||||
if (value.contains("产生时") || value.contains("实报") || value.contains("实付"))
|
||||
{
|
||||
return "产生时收取";
|
||||
}
|
||||
if (value.contains("旺季") || (period != null && period.start != null && value.contains("期间")))
|
||||
{
|
||||
return "旺季期间";
|
||||
}
|
||||
if (value.contains("固定收取") || value.contains("每票固定"))
|
||||
{
|
||||
return "固定收取";
|
||||
}
|
||||
if (period != null && period.start != null)
|
||||
{
|
||||
return "旺季期间";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String currencies(String text)
|
||||
{
|
||||
String source = text == null ? "" : text.toUpperCase(Locale.ROOT);
|
||||
StringBuilder joined = new StringBuilder();
|
||||
appendCurrency(joined, source, "GBP", "英镑");
|
||||
appendCurrency(joined, source, "EUR", "欧元");
|
||||
appendCurrency(joined, source, "USD", "美元");
|
||||
if (source.contains("RMB") || source.contains("CNY") || source.contains("人民币"))
|
||||
{
|
||||
appendCurrency(joined, "CNY", "CNY", "");
|
||||
}
|
||||
return joined.toString();
|
||||
}
|
||||
|
||||
public static Date toDate(LocalDate day)
|
||||
{
|
||||
if (day == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// 用普通日期,避免 java.sql.Date 在接口序列化时抛出不支持的时间类型
|
||||
return Date.from(day.atStartOfDay(ZoneId.of("GMT+8")).toInstant());
|
||||
}
|
||||
|
||||
private static void appendCurrency(StringBuilder joined, String source, String code, String alias)
|
||||
{
|
||||
if (source.contains(code) || (!alias.isEmpty() && source.contains(alias)))
|
||||
{
|
||||
if (joined.length() > 0)
|
||||
{
|
||||
joined.append(',');
|
||||
}
|
||||
joined.append(code);
|
||||
}
|
||||
}
|
||||
|
||||
private static String currency(String token)
|
||||
{
|
||||
if (token == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
String value = token.toUpperCase(Locale.ROOT);
|
||||
if (value.contains("GBP") || token.contains("英镑"))
|
||||
{
|
||||
return "GBP";
|
||||
}
|
||||
if (value.contains("EUR") || token.contains("欧元"))
|
||||
{
|
||||
return "EUR";
|
||||
}
|
||||
if (value.contains("USD") || token.contains("美元"))
|
||||
{
|
||||
return "USD";
|
||||
}
|
||||
if (value.contains("RMB") || value.contains("CNY") || token.contains("人民币"))
|
||||
{
|
||||
return "CNY";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String unit(String text)
|
||||
{
|
||||
String value = text.toUpperCase(Locale.ROOT);
|
||||
if (value.contains("/ITEM") || value.contains("每票") || text.contains("/票") || value.contains("/PARCEL"))
|
||||
{
|
||||
return "ITEM";
|
||||
}
|
||||
if (value.contains("/KG") || text.contains("/公斤") || text.contains("每公斤"))
|
||||
{
|
||||
return "KG";
|
||||
}
|
||||
if (text.contains("/次"))
|
||||
{
|
||||
return "次";
|
||||
}
|
||||
if (text.contains("%"))
|
||||
{
|
||||
return "%";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static int monthNumber(String name)
|
||||
{
|
||||
String[] months = { "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST",
|
||||
"SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" };
|
||||
String key = name.toUpperCase(Locale.ROOT);
|
||||
for (int i = 0; i < months.length; i++)
|
||||
{
|
||||
if (months[i].equals(key))
|
||||
{
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static LocalDate date(String year, String month, String day)
|
||||
{
|
||||
return date(Integer.parseInt(year), month, day);
|
||||
}
|
||||
|
||||
private static LocalDate date(int year, String month, String day)
|
||||
{
|
||||
return date(year, Integer.parseInt(month), day);
|
||||
}
|
||||
|
||||
private static LocalDate date(int year, int month, String day)
|
||||
{
|
||||
return LocalDate.of(year, month, Integer.parseInt(day));
|
||||
}
|
||||
|
||||
private static BigDecimal decimal(String raw)
|
||||
{
|
||||
return new BigDecimal(raw);
|
||||
}
|
||||
|
||||
/** 一段可以落库的生效窗口。text 是原文片段。 */
|
||||
public static final class Period
|
||||
{
|
||||
public static final Period NONE = new Period(null, null, null);
|
||||
|
||||
public final LocalDate start;
|
||||
|
||||
public final LocalDate end;
|
||||
|
||||
public final String text;
|
||||
|
||||
public Period(LocalDate start, LocalDate end, String text)
|
||||
{
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
/** 收费标准里的金额。unit 为 % 时 amount 是百分数本身。 */
|
||||
public static final class Money
|
||||
{
|
||||
public static final Money NONE = new Money(null, "", "");
|
||||
|
||||
public final BigDecimal amount;
|
||||
|
||||
public final String currency;
|
||||
|
||||
public final String unit;
|
||||
|
||||
public Money(BigDecimal amount, String currency, String unit)
|
||||
{
|
||||
this.amount = amount;
|
||||
this.currency = currency == null ? "" : currency;
|
||||
this.unit = unit == null ? "" : unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,315 @@
|
||||
package com.bms.price.pay;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import com.bms.price.domain.PayPostcodeLine;
|
||||
|
||||
/**
|
||||
* 价卡有多个地区时,用邮编在分区表里确定价格列。
|
||||
* 邮编不在分区表里、价卡又有「大陆」列时,按大陆计费。
|
||||
*/
|
||||
public final class PayZoneMatcher
|
||||
{
|
||||
private static final Pattern TOKEN = Pattern.compile("[a-z0-9]+|[\\u4e00-\\u9fa5]+");
|
||||
|
||||
private static final Set<String> GENERIC_TOKENS = Set.of("英国", "美国", "德国", "法国", "西班牙", "意大利", "挂号", "普货", "标快", "快递", "专线", "末端", "包裹");
|
||||
|
||||
private PayZoneMatcher()
|
||||
{
|
||||
}
|
||||
|
||||
/** 中英文连在一起时拆开,例如「英国evri」取出 evri,用来对邮编分区标题。 */
|
||||
public static List<String> searchTokens(String... texts)
|
||||
{
|
||||
Set<String> tokens = new LinkedHashSet<>();
|
||||
if (texts == null)
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
for (String text : texts)
|
||||
{
|
||||
if (text == null || text.isBlank())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Matcher matcher = TOKEN.matcher(text.toLowerCase(Locale.ROOT));
|
||||
while (matcher.find())
|
||||
{
|
||||
String token = matcher.group();
|
||||
if (token.length() >= 3 && !GENERIC_TOKENS.contains(token))
|
||||
{
|
||||
tokens.add(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(tokens);
|
||||
}
|
||||
|
||||
public static final class Decision
|
||||
{
|
||||
private final String zoneName;
|
||||
|
||||
private final String message;
|
||||
|
||||
private Decision(String zoneName, String message)
|
||||
{
|
||||
this.zoneName = zoneName;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static Decision zone(String zoneName)
|
||||
{
|
||||
return new Decision(zoneName, null);
|
||||
}
|
||||
|
||||
public static Decision fail(String message)
|
||||
{
|
||||
return new Decision(null, message);
|
||||
}
|
||||
|
||||
public static Decision unknown()
|
||||
{
|
||||
return new Decision(null, null);
|
||||
}
|
||||
|
||||
public String getZoneName()
|
||||
{
|
||||
return zoneName;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
public static Decision resolve(List<String> zoneNames, List<PayPostcodeLine> lines, String postcode)
|
||||
{
|
||||
if (zoneNames == null || zoneNames.size() <= 1)
|
||||
{
|
||||
return Decision.unknown();
|
||||
}
|
||||
if (postcode == null || postcode.isBlank())
|
||||
{
|
||||
return Decision.fail("订单没有邮编,无法从多个地区里确定运费");
|
||||
}
|
||||
String bfpo = bfpoZone(zoneNames, postcode);
|
||||
if (bfpo != null)
|
||||
{
|
||||
return Decision.zone(bfpo);
|
||||
}
|
||||
if (lines == null || lines.isEmpty())
|
||||
{
|
||||
return Decision.unknown();
|
||||
}
|
||||
String bestZone = null;
|
||||
int bestLength = -1;
|
||||
boolean tie = false;
|
||||
boolean unmapped = false;
|
||||
String unmappedName = "";
|
||||
for (PayPostcodeLine line : lines)
|
||||
{
|
||||
int length = matchedLength(line, postcode);
|
||||
if (length < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String region = firstText(line.getZoneName(), line.getRegionName());
|
||||
String zone = mapRegion(zoneNames, region);
|
||||
if (zone == null)
|
||||
{
|
||||
unmapped = true;
|
||||
unmappedName = region;
|
||||
continue;
|
||||
}
|
||||
if (length > bestLength)
|
||||
{
|
||||
bestLength = length;
|
||||
bestZone = zone;
|
||||
tie = false;
|
||||
}
|
||||
else if (length == bestLength && !zone.equals(bestZone))
|
||||
{
|
||||
tie = true;
|
||||
}
|
||||
}
|
||||
if (tie)
|
||||
{
|
||||
return Decision.fail("邮编 " + postcode.trim() + " 对上了多个地区,无法确定运费");
|
||||
}
|
||||
if (bestZone != null)
|
||||
{
|
||||
return Decision.zone(bestZone);
|
||||
}
|
||||
if (unmapped)
|
||||
{
|
||||
return Decision.fail("邮编 " + postcode.trim() + " 落在「" + unmappedName + "」,价卡没有同名地区");
|
||||
}
|
||||
String mainland = mainlandZone(zoneNames);
|
||||
if (mainland != null)
|
||||
{
|
||||
return Decision.zone(mainland);
|
||||
}
|
||||
return Decision.fail("邮编 " + postcode.trim() + " 没有对上价卡地区");
|
||||
}
|
||||
|
||||
public static String mapRegion(List<String> zoneNames, String region)
|
||||
{
|
||||
if (region == null || region.isBlank() || zoneNames == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String normRegion = norm(region);
|
||||
List<String> hits = new ArrayList<>();
|
||||
for (String zone : zoneNames)
|
||||
{
|
||||
String normZone = norm(zone);
|
||||
if (!normRegion.isEmpty() && (normZone.contains(normRegion) || normRegion.contains(normZone)))
|
||||
{
|
||||
hits.add(zone);
|
||||
}
|
||||
}
|
||||
if (hits.size() == 1)
|
||||
{
|
||||
return hits.get(0);
|
||||
}
|
||||
hits.clear();
|
||||
for (String word : region.toLowerCase(Locale.ROOT).split("[^a-z0-9\\u4e00-\\u9fa5]+"))
|
||||
{
|
||||
if (word.length() < 5)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (String zone : zoneNames)
|
||||
{
|
||||
if (norm(zone).contains(word) && !hits.contains(zone))
|
||||
{
|
||||
hits.add(zone);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hits.size() == 1)
|
||||
{
|
||||
return hits.get(0);
|
||||
}
|
||||
if (containsAny(region, "scottish", "shetland", "highland", "高地"))
|
||||
{
|
||||
return uniqueKeyword(zoneNames, "highlands", "高地");
|
||||
}
|
||||
if (containsAny(region, "scilly", "锡利"))
|
||||
{
|
||||
return uniqueKeyword(zoneNames, "scilly", "锡利", "海峡");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean sameZone(String left, String right)
|
||||
{
|
||||
String a = norm(left);
|
||||
String b = norm(right);
|
||||
return !a.isEmpty() && (a.equals(b) || a.contains(b) || b.contains(a));
|
||||
}
|
||||
|
||||
private static String bfpoZone(List<String> zoneNames, String postcode)
|
||||
{
|
||||
String compact = postcode.toUpperCase(Locale.ROOT).replaceAll("[^A-Z0-9]", "");
|
||||
if (!compact.startsWith("BFPO") && !compact.startsWith("BF"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return uniqueKeyword(zoneNames, "bfpo");
|
||||
}
|
||||
|
||||
private static String mainlandZone(List<String> zoneNames)
|
||||
{
|
||||
List<String> hits = new ArrayList<>();
|
||||
for (String zone : zoneNames)
|
||||
{
|
||||
String text = norm(zone);
|
||||
if (text.contains("mainland") || text.contains("大陆"))
|
||||
{
|
||||
hits.add(zone);
|
||||
}
|
||||
}
|
||||
return hits.size() == 1 ? hits.get(0) : null;
|
||||
}
|
||||
|
||||
private static int matchedLength(PayPostcodeLine line, String postcode)
|
||||
{
|
||||
int best = -1;
|
||||
best = longer(best, line.getPostcode(), postcode);
|
||||
best = longer(best, line.getPostcodeEnd(), postcode);
|
||||
return best;
|
||||
}
|
||||
|
||||
private static int longer(int best, String raw, String postcode)
|
||||
{
|
||||
if (raw == null || raw.isBlank())
|
||||
{
|
||||
return best;
|
||||
}
|
||||
for (String token : raw.split("[,,、|;;/]+"))
|
||||
{
|
||||
String text = token.trim();
|
||||
if (text.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (PaySurchargeRules.matchesPostcode(text, postcode) && text.length() > best)
|
||||
{
|
||||
best = text.length();
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private static String uniqueKeyword(List<String> zoneNames, String... keywords)
|
||||
{
|
||||
List<String> hits = new ArrayList<>();
|
||||
for (String zone : zoneNames)
|
||||
{
|
||||
String text = norm(zone);
|
||||
if (containsAny(text, keywords) && !hits.contains(zone))
|
||||
{
|
||||
hits.add(zone);
|
||||
}
|
||||
}
|
||||
return hits.size() == 1 ? hits.get(0) : null;
|
||||
}
|
||||
|
||||
private static boolean containsAny(String text, String... keywords)
|
||||
{
|
||||
String value = text == null ? "" : text.toLowerCase(Locale.ROOT);
|
||||
for (String keyword : keywords)
|
||||
{
|
||||
if (value.contains(keyword.toLowerCase(Locale.ROOT)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String firstText(String first, String second)
|
||||
{
|
||||
if (first != null && !first.isBlank())
|
||||
{
|
||||
return first.trim();
|
||||
}
|
||||
return second == null ? "" : second.trim();
|
||||
}
|
||||
|
||||
static String norm(String value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return value.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9\\u4e00-\\u9fa5]", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.price.domain.PayBill;
|
||||
import com.bms.price.domain.PayBillCommand;
|
||||
import com.bms.price.domain.PayBillPreview;
|
||||
|
||||
public interface IPayBillService
|
||||
{
|
||||
List<PayBill> selectPayBillList(PayBill query);
|
||||
|
||||
PayBill selectPayBillDetail(Long billId);
|
||||
|
||||
PayBillPreview preview(PayBillCommand command);
|
||||
|
||||
PayBill generate(PayBillCommand command);
|
||||
|
||||
int confirm(Long billId);
|
||||
|
||||
int markPaid(Long billId);
|
||||
|
||||
int voidBill(PayBillCommand command);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import com.bms.price.domain.PayOrderCostResult;
|
||||
|
||||
/** 按订单生成应付:渠道服务商上的应付产品,用订单基础数据计算。 */
|
||||
public interface IPayOrderCostService
|
||||
{
|
||||
PayOrderCostResult generate(String billId);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.price.domain.PayPostcodeGroup;
|
||||
import com.bms.price.domain.PayPostcodeImportResult;
|
||||
import com.bms.price.domain.PayPostcodeLine;
|
||||
|
||||
/** 数据管理下的应付邮编配置 */
|
||||
public interface IPayPostcodeService
|
||||
{
|
||||
List<PayPostcodeGroup> selectGroupList(PayPostcodeGroup query);
|
||||
|
||||
PayPostcodeGroup selectGroupById(Long groupId);
|
||||
|
||||
int updateGroup(PayPostcodeGroup group);
|
||||
|
||||
List<PayPostcodeLine> selectLineList(PayPostcodeLine query);
|
||||
|
||||
PayPostcodeImportResult importExcel(byte[] bytes, String filename, String username);
|
||||
|
||||
int deleteByIds(Long[] groupIds);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
import com.bms.price.domain.PayPreview;
|
||||
import com.bms.price.domain.PayPriceCompare;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
import com.bms.price.domain.PayProductTerm;
|
||||
import com.bms.price.domain.PaySaveCommand;
|
||||
|
||||
/**
|
||||
* 应付价卡配置:预览后保存,列表查询和导出。
|
||||
*/
|
||||
public interface IPayProductService
|
||||
{
|
||||
/**
|
||||
* 按字典匹配工作表并识别,不写数据库。
|
||||
* dictValue 有值时只读这一张,为空时读取文件里所有能对上的工作表。
|
||||
*/
|
||||
PayPreview preview(byte[] bytes, String filename, String dictValue);
|
||||
|
||||
/** 把预览里勾选的产品写入数据库。同名字典产品再次导入时另存一版,旧价格留在有效期表里 */
|
||||
String save(PaySaveCommand command);
|
||||
|
||||
/** 一个产品的全部价格版本 */
|
||||
List<PayProductTerm> listTerms(Long productId);
|
||||
|
||||
/** 修改某一版的生效起止 */
|
||||
int updateTerm(PayProductTerm term);
|
||||
|
||||
/** 0 生效,1 失效 */
|
||||
int changeTermStatus(PayProductTerm term);
|
||||
|
||||
/** 修改已保存的产品,重量段和附加费按提交内容整表替换 */
|
||||
int updateDetail(PayProduct product);
|
||||
|
||||
/** 公共模板下拉。payType 为空时按应付 AP */
|
||||
List<PayProduct> selectTemplateOptions(String payType);
|
||||
|
||||
/** 渠道绑定的产品。返回的 originId 是不变的产品编号,价格是当前有效版 */
|
||||
PayProduct selectChannelPrice(String channelId);
|
||||
|
||||
/** 只绑定产品编号,不产生新版本。payType 为空时按应付 AP */
|
||||
void bindChannel(String channelId, Long productId, Long priceSetId, String payType);
|
||||
|
||||
/** 取消渠道上指定收付类型的产品。payType 为空时按应付 AP */
|
||||
void unbindChannel(String channelId, String payType);
|
||||
|
||||
/** 按订单时间取这一产品当时有效的价格版本 */
|
||||
PayProduct selectPriceForDate(String channelId, Date bizDate);
|
||||
|
||||
/** 对照两个版本的重量段和附加费 */
|
||||
PayPriceCompare compare(Long leftId, Long rightId);
|
||||
|
||||
/** 按编辑结果给同一产品编号追加一版,渠道绑定保持原产品编号 */
|
||||
PayProduct saveChannelVersion(String channelId, PayProduct edited);
|
||||
|
||||
/** 重新导入价格,仍记在原来的产品编号下 */
|
||||
PayProduct reimportChannel(String channelId, byte[] bytes, String filename);
|
||||
|
||||
List<PayProduct> selectPayProductList(PayProduct query);
|
||||
|
||||
/** 列表条件对应的产品,并带上重量段、附加费和其他配置 */
|
||||
List<PayProduct> selectPayProductDetails(PayProduct query);
|
||||
|
||||
/** 产品及其重量段、附加费、邮编配置 */
|
||||
PayProduct selectPayProductDetail(Long productId);
|
||||
|
||||
int deletePayProducts(Long[] productIds);
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
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.SecurityUtils;
|
||||
import com.bms.common.utils.uuid.IdUtils;
|
||||
import com.bms.customer.supplierinfo.domain.BmsSupplier;
|
||||
import com.bms.customer.supplierinfo.mapper.BmsSupplierMapper;
|
||||
import com.bms.price.domain.PayBill;
|
||||
import com.bms.price.domain.PayBillCommand;
|
||||
import com.bms.price.domain.PayBillLine;
|
||||
import com.bms.price.domain.PayBillLog;
|
||||
import com.bms.price.domain.PayBillOrder;
|
||||
import com.bms.price.domain.PayBillPreview;
|
||||
import com.bms.price.domain.PayOrderCostResult;
|
||||
import com.bms.price.domain.PrcPayBillFee;
|
||||
import com.bms.price.mapper.PayBillMapper;
|
||||
import com.bms.price.service.IPayBillService;
|
||||
import com.bms.price.service.IPayOrderCostService;
|
||||
|
||||
/**
|
||||
* 应付账单。先按订单号、计费日或提单查出订单,再把当时的费用抄进账单。作废只改状态。
|
||||
*/
|
||||
@Service
|
||||
public class PayBillServiceImpl implements IPayBillService
|
||||
{
|
||||
private static final int MAX_ORDERS = 2000;
|
||||
|
||||
@Autowired
|
||||
private PayBillMapper payBillMapper;
|
||||
|
||||
@Autowired
|
||||
private IPayOrderCostService payOrderCostService;
|
||||
|
||||
@Autowired
|
||||
private BmsSupplierMapper supplierMapper;
|
||||
|
||||
@Override
|
||||
public List<PayBill> selectPayBillList(PayBill query)
|
||||
{
|
||||
return payBillMapper.selectPayBillList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayBill selectPayBillDetail(Long billId)
|
||||
{
|
||||
PayBill bill = payBillMapper.selectPayBillById(billId);
|
||||
if (bill == null)
|
||||
{
|
||||
throw new ServiceException("应付账单不存在");
|
||||
}
|
||||
bill.setLines(payBillMapper.selectLines(billId));
|
||||
bill.setLogs(payBillMapper.selectLogs(billId));
|
||||
return bill;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayBillPreview preview(PayBillCommand command)
|
||||
{
|
||||
return collect(command, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PayBill generate(PayBillCommand command)
|
||||
{
|
||||
if (command.getSupplierId() == null)
|
||||
{
|
||||
throw new ServiceException("请选择供应商");
|
||||
}
|
||||
BmsSupplier supplier = supplierMapper.selectBmsSupplierBySupplierId(command.getSupplierId());
|
||||
if (supplier == null)
|
||||
{
|
||||
throw new ServiceException("供应商不存在");
|
||||
}
|
||||
PayBillPreview preview = collect(command, true);
|
||||
List<PayBillOrder> orders = freeOrders(preview);
|
||||
if (orders.isEmpty())
|
||||
{
|
||||
throw new ServiceException(preview.getMissingNos().isEmpty() ? "没有可生成的订单" : "订单号没有对上:" + String.join("、", preview.getMissingNos()));
|
||||
}
|
||||
String currency = currencyOf(orders);
|
||||
Map<String, Integer> versions = new HashMap<>();
|
||||
for (PayBillOrder order : orders)
|
||||
{
|
||||
PayOrderCostResult cost = payOrderCostService.generate(order.getBillId());
|
||||
if (cost != null)
|
||||
{
|
||||
versions.put(order.getBillId(), cost.getVersionNo());
|
||||
}
|
||||
}
|
||||
List<String> billIds = new ArrayList<>();
|
||||
for (PayBillOrder order : orders)
|
||||
{
|
||||
billIds.add(order.getBillId());
|
||||
}
|
||||
List<PrcPayBillFee> fees = billIds.isEmpty() ? new ArrayList<>() : payBillMapper.selectOrderFees(billIds);
|
||||
if (!fees.isEmpty() && fees.get(0).getCurrency() != null && !fees.get(0).getCurrency().isBlank())
|
||||
{
|
||||
currency = fees.get(0).getCurrency();
|
||||
}
|
||||
List<PayBillLine> lines = new ArrayList<>();
|
||||
for (PrcPayBillFee fee : fees)
|
||||
{
|
||||
PayBillOrder order = findOrder(orders, fee.getBillId());
|
||||
PayBillLine line = new PayBillLine();
|
||||
line.setOrderBillId(fee.getBillId());
|
||||
line.setOrderNo(order == null ? "" : order.getOrderNo());
|
||||
line.setLadingNo(order == null ? "" : order.getLadingNo());
|
||||
line.setFeeName(fee.getFeeName() == null || fee.getFeeName().isBlank() ? fee.getFeeType() : fee.getFeeName());
|
||||
line.setAmount(fee.getAmount());
|
||||
line.setCurrency(fee.getCurrency());
|
||||
line.setPriceDate(order == null || order.getBizDate() == null ? null : order.getBizDate());
|
||||
line.setVersionNo(versions.get(fee.getBillId()));
|
||||
line.setLineSource("ORDER");
|
||||
lines.add(line);
|
||||
}
|
||||
if (lines.isEmpty())
|
||||
{
|
||||
throw new ServiceException("订单没有应付费用");
|
||||
}
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (PayBillLine line : lines)
|
||||
{
|
||||
String lineCurrency = line.getCurrency() == null ? "" : line.getCurrency();
|
||||
if (!currency.equals(lineCurrency))
|
||||
{
|
||||
throw new ServiceException("所选订单币种不一致,请按币种分开生成");
|
||||
}
|
||||
total = total.add(line.getAmount() == null ? BigDecimal.ZERO : line.getAmount());
|
||||
}
|
||||
String operator = SecurityUtils.getUsername();
|
||||
PayBill bill = new PayBill();
|
||||
bill.setBillNo(nextNo());
|
||||
bill.setSupplierId(supplier.getSupplierId());
|
||||
bill.setSupplierName(supplier.getSupplierName());
|
||||
bill.setCurrency(currency);
|
||||
bill.setSourceType(mode(command));
|
||||
bill.setPeriodStart(parseDay(command.getBeginDate()));
|
||||
bill.setPeriodEnd(parseDay(command.getEndDate()));
|
||||
bill.setOrderCount(orders.size());
|
||||
bill.setTotalAmount(total.setScale(2, RoundingMode.HALF_UP));
|
||||
bill.setCreateBy(operator);
|
||||
bill.setRemark(command.getRemark());
|
||||
payBillMapper.insertPayBill(bill);
|
||||
for (PayBillLine line : lines)
|
||||
{
|
||||
line.setBillId(bill.getBillId());
|
||||
}
|
||||
payBillMapper.insertLines(lines);
|
||||
writeLog(bill.getBillId(), "CREATE", null, "DRAFT", "生成应付账单,订单 " + orders.size() + " 票", operator);
|
||||
return selectPayBillDetail(bill.getBillId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int confirm(Long billId)
|
||||
{
|
||||
PayBill bill = required(billId);
|
||||
if (!"DRAFT".equals(bill.getStatus()))
|
||||
{
|
||||
throw new ServiceException("只有草稿可以确认");
|
||||
}
|
||||
String operator = SecurityUtils.getUsername();
|
||||
bill.setStatus("CONFIRMED");
|
||||
bill.setUpdateBy(operator);
|
||||
payBillMapper.updatePayBillStatus(bill);
|
||||
writeLog(billId, "CONFIRM", "DRAFT", "CONFIRMED", "确认应付账单", operator);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int markPaid(Long billId)
|
||||
{
|
||||
PayBill bill = required(billId);
|
||||
if (!"CONFIRMED".equals(bill.getStatus()))
|
||||
{
|
||||
throw new ServiceException("请先确认账单,再登记付款");
|
||||
}
|
||||
String operator = SecurityUtils.getUsername();
|
||||
bill.setStatus("PAID");
|
||||
bill.setUpdateBy(operator);
|
||||
payBillMapper.updatePayBillStatus(bill);
|
||||
writeLog(billId, "PAY", "CONFIRMED", "PAID", "登记付款", operator);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int voidBill(PayBillCommand command)
|
||||
{
|
||||
if (command == null || command.getBillId() == null)
|
||||
{
|
||||
throw new ServiceException("请选择应付账单");
|
||||
}
|
||||
if (command.getVoidReason() == null || command.getVoidReason().isBlank())
|
||||
{
|
||||
throw new ServiceException("请填写作废原因");
|
||||
}
|
||||
PayBill bill = required(command.getBillId());
|
||||
if ("VOID".equals(bill.getStatus()))
|
||||
{
|
||||
throw new ServiceException("账单已经作废");
|
||||
}
|
||||
if ("PAID".equals(bill.getStatus()))
|
||||
{
|
||||
throw new ServiceException("已付款的账单不能直接作废,请先冲回付款");
|
||||
}
|
||||
String operator = SecurityUtils.getUsername();
|
||||
String from = bill.getStatus();
|
||||
bill.setStatus("VOID");
|
||||
bill.setVoidReason(command.getVoidReason().trim());
|
||||
bill.setVoidBy(operator);
|
||||
bill.setVoidTime(new Date());
|
||||
bill.setUpdateBy(operator);
|
||||
payBillMapper.updatePayBillStatus(bill);
|
||||
writeLog(bill.getBillId(), "VOID", from, "VOID", command.getVoidReason().trim(), operator);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private PayBillPreview collect(PayBillCommand command, boolean forGenerate)
|
||||
{
|
||||
String mode = mode(command);
|
||||
List<String> orderNos = "ORDER".equals(mode) ? tokens(command.getOrderText()) : new ArrayList<>();
|
||||
List<String> ladingNos = "LADING".equals(mode) ? tokens(command.getLadingText()) : new ArrayList<>();
|
||||
if ("ORDER".equals(mode) && orderNos.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请填写订单号");
|
||||
}
|
||||
if ("TIME".equals(mode) && (command.getBeginDate() == null || command.getBeginDate().isBlank()
|
||||
|| command.getEndDate() == null || command.getEndDate().isBlank()))
|
||||
{
|
||||
throw new ServiceException("请选择计费日起止");
|
||||
}
|
||||
if ("LADING".equals(mode) && ladingNos.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请填写提单号");
|
||||
}
|
||||
List<PayBillOrder> found = payBillMapper.selectCandidateOrders(
|
||||
orderNos.isEmpty() ? null : orderNos,
|
||||
"TIME".equals(mode) ? command.getBeginDate() : null,
|
||||
"TIME".equals(mode) ? command.getEndDate() : null,
|
||||
ladingNos.isEmpty() ? null : ladingNos);
|
||||
if ("TIME".equals(mode))
|
||||
{
|
||||
found.removeIf(order -> order.getBizDate() == null);
|
||||
}
|
||||
PayBillPreview preview = new PayBillPreview();
|
||||
if ("ORDER".equals(mode))
|
||||
{
|
||||
for (String no : orderNos)
|
||||
{
|
||||
boolean hit = false;
|
||||
for (PayBillOrder order : found)
|
||||
{
|
||||
if (no.equals(order.getOrderNo()))
|
||||
{
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hit)
|
||||
{
|
||||
preview.getMissingNos().add(no);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (PayBillOrder order : found)
|
||||
{
|
||||
if (order.getOccupiedBillNo() != null && !order.getOccupiedBillNo().isBlank())
|
||||
{
|
||||
preview.getOccupied().add(order);
|
||||
}
|
||||
else
|
||||
{
|
||||
preview.getOrders().add(order);
|
||||
}
|
||||
}
|
||||
if (preview.getOrders().size() >= MAX_ORDERS)
|
||||
{
|
||||
preview.setMessage("一次最多处理 " + MAX_ORDERS + " 票,请缩小范围");
|
||||
}
|
||||
if (!forGenerate && !preview.getMissingNos().isEmpty())
|
||||
{
|
||||
preview.setMessage("未找到订单:" + String.join("、", preview.getMissingNos()));
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
|
||||
private List<PayBillOrder> freeOrders(PayBillPreview preview)
|
||||
{
|
||||
return preview.getOrders();
|
||||
}
|
||||
|
||||
private String currencyOf(List<PayBillOrder> orders)
|
||||
{
|
||||
String currency = "";
|
||||
for (PayBillOrder order : orders)
|
||||
{
|
||||
if (order.getCurrency() != null && !order.getCurrency().isBlank())
|
||||
{
|
||||
currency = order.getCurrency();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return currency.isEmpty() ? "CNY" : currency;
|
||||
}
|
||||
|
||||
private PayBillOrder findOrder(List<PayBillOrder> orders, String billId)
|
||||
{
|
||||
for (PayBillOrder order : orders)
|
||||
{
|
||||
if (order.getBillId().equals(billId))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PayBill required(Long billId)
|
||||
{
|
||||
PayBill bill = payBillMapper.selectPayBillById(billId);
|
||||
if (bill == null)
|
||||
{
|
||||
throw new ServiceException("应付账单不存在");
|
||||
}
|
||||
return bill;
|
||||
}
|
||||
|
||||
private void writeLog(Long billId, String type, String from, String to, String desc, String operator)
|
||||
{
|
||||
PayBillLog log = new PayBillLog();
|
||||
log.setBillId(billId);
|
||||
log.setOperationType(type);
|
||||
log.setFromStatus(from);
|
||||
log.setToStatus(to);
|
||||
log.setOperationDesc(desc);
|
||||
log.setOperatorName(operator);
|
||||
payBillMapper.insertLog(log);
|
||||
}
|
||||
|
||||
private String nextNo()
|
||||
{
|
||||
return "AP" + new SimpleDateFormat("yyyyMM").format(new Date())
|
||||
+ IdUtils.fastSimpleUUID().substring(0, 8).toUpperCase();
|
||||
}
|
||||
|
||||
private Date parseDay(String text)
|
||||
{
|
||||
if (text == null || text.isBlank())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
LocalDate day = LocalDate.parse(text.trim());
|
||||
return java.sql.Date.valueOf(day);
|
||||
}
|
||||
|
||||
private String mode(PayBillCommand command)
|
||||
{
|
||||
if (command == null || command.getSourceType() == null || command.getSourceType().isBlank())
|
||||
{
|
||||
throw new ServiceException("请选择查询方式");
|
||||
}
|
||||
return command.getSourceType().trim().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
static List<String> tokens(String text)
|
||||
{
|
||||
LinkedHashSet<String> values = new LinkedHashSet<>();
|
||||
if (text == null)
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
for (String part : text.split("[,,、;;\\s]+"))
|
||||
{
|
||||
String token = part.trim();
|
||||
if (!token.isEmpty())
|
||||
{
|
||||
values.add(token);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(values);
|
||||
}
|
||||
}
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
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.SecurityUtils;
|
||||
import com.bms.masterdata.domain.ServiceChannel;
|
||||
import com.bms.masterdata.mapper.ServiceChannelMapper;
|
||||
import com.bms.price.domain.PayCostLine;
|
||||
import com.bms.price.domain.PayOrderCostResult;
|
||||
import com.bms.price.domain.PayPostcodeGroup;
|
||||
import com.bms.price.domain.PayPostcodeLine;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
import com.bms.price.domain.PayWeightBand;
|
||||
import com.bms.price.domain.PrcOrderBill;
|
||||
import com.bms.price.domain.PrcOrderBillExt;
|
||||
import com.bms.price.domain.PrcPayBill;
|
||||
import com.bms.price.domain.PrcPayBillFee;
|
||||
import com.bms.price.mapper.PayPostcodeMapper;
|
||||
import com.bms.price.mapper.PrcOrderBillExtMapper;
|
||||
import com.bms.price.mapper.PrcOrderBillMapper;
|
||||
import com.bms.price.mapper.PrcPayBillFeeMapper;
|
||||
import com.bms.price.mapper.PrcPayBillMapper;
|
||||
import com.bms.price.pay.PayCostCalculator;
|
||||
import com.bms.price.pay.PayZoneMatcher;
|
||||
import com.bms.price.service.IPayOrderCostService;
|
||||
import com.bms.price.service.IPayProductService;
|
||||
|
||||
/**
|
||||
* 订单找到渠道,渠道找到当前应付产品,再按重量、尺寸、邮编计算应付并落库。
|
||||
*/
|
||||
@Service
|
||||
public class PayOrderCostServiceImpl implements IPayOrderCostService
|
||||
{
|
||||
@Autowired
|
||||
private PrcOrderBillMapper orderBillMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcOrderBillExtMapper orderBillExtMapper;
|
||||
|
||||
@Autowired
|
||||
private ServiceChannelMapper serviceChannelMapper;
|
||||
|
||||
@Autowired
|
||||
private IPayProductService payProductService;
|
||||
|
||||
@Autowired
|
||||
private PrcPayBillMapper payBillMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcPayBillFeeMapper payBillFeeMapper;
|
||||
|
||||
@Autowired
|
||||
private PayPostcodeMapper payPostcodeMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PayOrderCostResult generate(String billId)
|
||||
{
|
||||
if (billId == null || billId.isBlank())
|
||||
{
|
||||
throw new ServiceException("请选择订单");
|
||||
}
|
||||
PrcOrderBill bill = orderBillMapper.selectPrcOrderBillById(billId.trim());
|
||||
if (bill == null)
|
||||
{
|
||||
throw new ServiceException("订单不存在");
|
||||
}
|
||||
PrcOrderBillExt ext = orderBillExtMapper.selectByBillId(bill.getBillId());
|
||||
ServiceChannel channel = resolveChannel(ext);
|
||||
java.util.Date bizDate = bill.getBizDate() != null ? bill.getBizDate() : bill.getOrderTime();
|
||||
PayProduct product = payProductService.selectPriceForDate(channel.getId(), bizDate);
|
||||
BigDecimal weight = firstPositive(bill.getChargeWeight(), bill.getWeight(), bill.getActualWeight(), bill.getReceiveWeight());
|
||||
Integer orderStatus = bill.getOrderStatus() != null ? bill.getOrderStatus() : (ext == null ? null : ext.getOrderStatus());
|
||||
String zoneName = resolveZone(channel, product, bill.getDestPostcode(), bill.getCountryCode(), bill.getCountryName());
|
||||
PayOrderCostResult result = PayCostCalculator.calculate(product, weight, bill.getPkgLength(), bill.getPkgWidth(),
|
||||
bill.getPkgHeight(), bill.getDestPostcode(), zoneName, bizDate, orderStatus);
|
||||
result.setBillId(bill.getBillId());
|
||||
result.setChannelId(channel.getId());
|
||||
result.setChannelName(channel.getChannelName());
|
||||
result.setVersionNo(product.getVersionNo());
|
||||
if (result.getFees() == null || result.getFees().isEmpty())
|
||||
{
|
||||
throw new ServiceException(result.getMessage() == null ? "没有算出应付费用" : result.getMessage());
|
||||
}
|
||||
String operator = SecurityUtils.getUsername();
|
||||
PrcPayBill payBill = saveBill(bill, result, operator);
|
||||
saveFees(bill.getBillId(), payBill.getPayBillId(), result.getFees());
|
||||
orderBillMapper.updatePayTotal(bill.getBillId(), result.getPayTotal());
|
||||
String channelName = channel.getChannelName() == null ? channel.getChannelCode() : channel.getChannelName();
|
||||
String end = product.getTermEnd() == null ? "不限" : day(product.getTermEnd());
|
||||
result.setMessage("渠道「" + channelName + "」产品编号 " + product.getOriginId() + " 第 " + product.getVersionNo()
|
||||
+ " 版(" + day(product.getTermStart()) + " 至 " + end + ")," + result.getMessage());
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 价卡分成多个地区时,用邮编在对应分区表里选出价格列。 */
|
||||
private String resolveZone(ServiceChannel channel, PayProduct product, String postcode, String countryCode, String countryName)
|
||||
{
|
||||
List<String> zoneNames = zoneNames(product);
|
||||
if (zoneNames.size() <= 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
List<String> tokens = PayZoneMatcher.searchTokens(product.getProductName(), product.getSheetName());
|
||||
if (tokens.isEmpty())
|
||||
{
|
||||
tokens = PayZoneMatcher.searchTokens(channel.getChannelName(), channel.getChannelCode());
|
||||
}
|
||||
if (tokens.isEmpty())
|
||||
{
|
||||
throw new ServiceException("产品「" + product.getProductName() + "」有多个地区,订单邮编无法对应到产品");
|
||||
}
|
||||
List<PayPostcodeGroup> groups = new ArrayList<>();
|
||||
for (PayPostcodeGroup group : payPostcodeMapper.selectGroupsByTokens(tokens))
|
||||
{
|
||||
if (sameCountry(group, countryCode, countryName))
|
||||
{
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
if (groups.isEmpty())
|
||||
{
|
||||
throw new ServiceException("产品「" + product.getProductName() + "」有多个地区,没有找到对应的邮编分区");
|
||||
}
|
||||
List<Long> groupIds = new ArrayList<>();
|
||||
for (PayPostcodeGroup group : groups)
|
||||
{
|
||||
groupIds.add(group.getGroupId());
|
||||
}
|
||||
Set<Long> usable = new LinkedHashSet<>();
|
||||
for (PayPostcodeLine key : payPostcodeMapper.selectRegionKeys(groupIds))
|
||||
{
|
||||
String region = key.getZoneName() != null && !key.getZoneName().isBlank() ? key.getZoneName() : key.getRegionName();
|
||||
if (PayZoneMatcher.mapRegion(zoneNames, region) != null)
|
||||
{
|
||||
usable.add(key.getGroupId());
|
||||
}
|
||||
}
|
||||
if (usable.isEmpty())
|
||||
{
|
||||
throw new ServiceException("产品「" + product.getProductName() + "」有多个地区,邮编分区对不上价卡上的地区");
|
||||
}
|
||||
PayZoneMatcher.Decision decision = PayZoneMatcher.resolve(zoneNames, payPostcodeMapper.selectLinesByGroups(new ArrayList<>(usable)), postcode);
|
||||
if (decision.getMessage() != null)
|
||||
{
|
||||
throw new ServiceException(decision.getMessage());
|
||||
}
|
||||
if (decision.getZoneName() == null)
|
||||
{
|
||||
throw new ServiceException("邮编没有对上产品「" + product.getProductName() + "」的地区");
|
||||
}
|
||||
return decision.getZoneName();
|
||||
}
|
||||
|
||||
private static List<String> zoneNames(PayProduct product)
|
||||
{
|
||||
Set<String> names = new LinkedHashSet<>();
|
||||
if (product.getWeightBands() == null)
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
for (PayWeightBand band : product.getWeightBands())
|
||||
{
|
||||
if (band != null && band.getZoneName() != null && !band.getZoneName().isBlank())
|
||||
{
|
||||
names.add(band.getZoneName().trim());
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(names);
|
||||
}
|
||||
|
||||
private static boolean sameCountry(PayPostcodeGroup group, String countryCode, String countryName)
|
||||
{
|
||||
if (blank(countryCode) && blank(countryName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (blank(group.getCountryCode()) && blank(group.getCountryName()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!blank(countryCode) && countryCode.equalsIgnoreCase(group.getCountryCode()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return !blank(countryName) && countryName.equals(group.getCountryName());
|
||||
}
|
||||
|
||||
private ServiceChannel resolveChannel(PrcOrderBillExt ext)
|
||||
{
|
||||
if (ext == null)
|
||||
{
|
||||
throw new ServiceException("订单没有渠道服务商");
|
||||
}
|
||||
ServiceChannel channel = blank(ext.getProductChannelId()) ? null : serviceChannelMapper.selectServiceChannelById(ext.getProductChannelId().trim());
|
||||
if (channel == null && !blank(ext.getProductFloorCode()))
|
||||
{
|
||||
channel = serviceChannelMapper.selectServiceChannelByCode(ext.getProductFloorCode().trim());
|
||||
}
|
||||
if (channel == null && !blank(ext.getProductFloor()))
|
||||
{
|
||||
List<ServiceChannel> named = serviceChannelMapper.selectServiceChannelByName(ext.getProductFloor().trim());
|
||||
if (named != null && named.size() > 1)
|
||||
{
|
||||
throw new ServiceException("渠道名称「" + ext.getProductFloor().trim() + "」对应了多条渠道,请先在订单上指定渠道");
|
||||
}
|
||||
channel = named == null || named.isEmpty() ? null : named.get(0);
|
||||
}
|
||||
if (channel == null)
|
||||
{
|
||||
throw new ServiceException("订单没有渠道服务商,或渠道尚未同步");
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
private PrcPayBill saveBill(PrcOrderBill bill, PayOrderCostResult result, String operator)
|
||||
{
|
||||
PrcPayBill existed = payBillMapper.selectByBillId(bill.getBillId());
|
||||
PrcPayBill row = existed == null ? new PrcPayBill() : existed;
|
||||
if (existed == null)
|
||||
{
|
||||
row.setPayBillId(UUID.randomUUID().toString());
|
||||
row.setBillId(bill.getBillId());
|
||||
row.setCreateBy(operator);
|
||||
}
|
||||
row.setCurrency(result.getCurrency());
|
||||
row.setCostAmount(result.getPayTotal());
|
||||
row.setPayTotal(result.getPayTotal());
|
||||
row.setBelongSaler(bill.getBelongSaler());
|
||||
row.setBelongSalerCode(bill.getBelongSalerCode());
|
||||
row.setSettlementMan(bill.getSettlementMan());
|
||||
row.setSettlementManCode(bill.getSettlementManCode());
|
||||
row.setUpdateBy(operator);
|
||||
if (existed == null)
|
||||
{
|
||||
payBillMapper.insertPrcPayBill(row);
|
||||
}
|
||||
else
|
||||
{
|
||||
payBillMapper.updatePrcPayBill(row);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
private void saveFees(String billId, String payBillId, List<PayCostLine> fees)
|
||||
{
|
||||
payBillFeeMapper.deleteByBillId(billId);
|
||||
List<PrcPayBillFee> rows = new java.util.ArrayList<>();
|
||||
long fallback = 1L;
|
||||
for (PayCostLine fee : fees)
|
||||
{
|
||||
PrcPayBillFee row = new PrcPayBillFee();
|
||||
row.setFeeId(UUID.randomUUID().toString());
|
||||
row.setBillId(billId);
|
||||
row.setPayBillId(payBillId);
|
||||
row.setFeeType(fee.getFeeType());
|
||||
row.setFeeName(fee.getFeeName());
|
||||
long scId = fee.getScId() == null ? 0L : fee.getScId();
|
||||
if ("SURCHARGE".equals(fee.getFeeType()) && scId == 0L)
|
||||
{
|
||||
scId = fallback++;
|
||||
}
|
||||
row.setScId(scId);
|
||||
row.setAmount(fee.getAmount());
|
||||
row.setCurrency(fee.getCurrency());
|
||||
row.setRoundMode("HALF_UP");
|
||||
row.setRemark(fee.getRemark());
|
||||
rows.add(row);
|
||||
}
|
||||
if (!rows.isEmpty())
|
||||
{
|
||||
payBillFeeMapper.insertBatch(rows);
|
||||
}
|
||||
}
|
||||
|
||||
private static String day(java.util.Date date)
|
||||
{
|
||||
if (date == null)
|
||||
{
|
||||
return "不限";
|
||||
}
|
||||
if (date instanceof java.sql.Date sql)
|
||||
{
|
||||
return sql.toLocalDate().toString();
|
||||
}
|
||||
return date.toInstant().atZone(java.time.ZoneId.of("GMT+8")).toLocalDate().toString();
|
||||
}
|
||||
|
||||
private static BigDecimal firstPositive(BigDecimal... values)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (BigDecimal value : values)
|
||||
{
|
||||
if (value != null && value.compareTo(BigDecimal.ZERO) > 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean blank(String value)
|
||||
{
|
||||
return value == null || value.isBlank();
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
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.price.domain.PayPostcodeGroup;
|
||||
import com.bms.price.domain.PayPostcodeImportResult;
|
||||
import com.bms.price.domain.PayPostcodeLine;
|
||||
import com.bms.price.mapper.PayPostcodeMapper;
|
||||
import com.bms.price.pay.PayPostcodeImporter;
|
||||
import com.bms.price.service.IPayPostcodeService;
|
||||
|
||||
/**
|
||||
* 导入时按文件和工作表覆盖。附加费在解析阶段已经按国家和金额合并。
|
||||
*/
|
||||
@Service
|
||||
public class PayPostcodeServiceImpl implements IPayPostcodeService
|
||||
{
|
||||
private static final int BATCH = 400;
|
||||
|
||||
@Autowired
|
||||
private PayPostcodeMapper payPostcodeMapper;
|
||||
|
||||
private final PayPostcodeImporter importer = new PayPostcodeImporter();
|
||||
|
||||
@Override
|
||||
public List<PayPostcodeGroup> selectGroupList(PayPostcodeGroup query)
|
||||
{
|
||||
return payPostcodeMapper.selectGroupList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayPostcodeGroup selectGroupById(Long groupId)
|
||||
{
|
||||
return payPostcodeMapper.selectGroupById(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateGroup(PayPostcodeGroup group)
|
||||
{
|
||||
if (group == null || group.getGroupId() == null)
|
||||
{
|
||||
throw new ServiceException("请选择要修改的邮编配置");
|
||||
}
|
||||
if (payPostcodeMapper.selectGroupById(group.getGroupId()) == null)
|
||||
{
|
||||
throw new ServiceException("邮编配置不存在或已删除");
|
||||
}
|
||||
group.setCoverage(normalizeCoverage(group.getCoverage()));
|
||||
group.setChannelName(trimToEmpty(group.getChannelName()));
|
||||
group.setCountryName(trimToEmpty(group.getCountryName()));
|
||||
group.setCountryCode(trimToEmpty(group.getCountryCode()));
|
||||
group.setServiceType(trimToEmpty(group.getServiceType()));
|
||||
group.setCatalogCountry(trimToEmpty(group.getCatalogCountry()));
|
||||
group.setProductName(trimToEmpty(group.getProductName()));
|
||||
group.setQuoteSheet(trimToEmpty(group.getQuoteSheet()));
|
||||
group.setFeeName(trimToEmpty(group.getFeeName()));
|
||||
group.setFeeText(trimToEmpty(group.getFeeText()));
|
||||
group.setBlockTitle(trimToEmpty(group.getBlockTitle()));
|
||||
group.setRemark(trimToEmpty(group.getRemark()));
|
||||
if (!"FEE".equals(group.getConfigKind()) && !"ZONE".equals(group.getConfigKind()))
|
||||
{
|
||||
throw new ServiceException("类型只能是附加费或邮编分区");
|
||||
}
|
||||
return payPostcodeMapper.updateGroup(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PayPostcodeLine> selectLineList(PayPostcodeLine query)
|
||||
{
|
||||
return payPostcodeMapper.selectLineList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PayPostcodeImportResult importExcel(byte[] bytes, String filename, String username)
|
||||
{
|
||||
List<PayPostcodeGroup> groups = importer.parse(bytes, filename);
|
||||
if (groups.isEmpty())
|
||||
{
|
||||
throw new ServiceException("没有识别到邮编或附加费邮编,请确认表头里有邮编、ZIP 或附加费");
|
||||
}
|
||||
Set<String> sheets = new LinkedHashSet<>();
|
||||
for (PayPostcodeGroup group : groups)
|
||||
{
|
||||
sheets.add(group.getSheetName());
|
||||
}
|
||||
String md5 = groups.get(0).getFileMd5();
|
||||
for (String sheet : sheets)
|
||||
{
|
||||
List<Long> oldIds = payPostcodeMapper.selectGroupIds(md5, sheet);
|
||||
if (oldIds != null && !oldIds.isEmpty())
|
||||
{
|
||||
payPostcodeMapper.deleteLines(oldIds);
|
||||
payPostcodeMapper.deleteGroups(oldIds);
|
||||
}
|
||||
}
|
||||
int feeCount = 0;
|
||||
int zoneCount = 0;
|
||||
int lineCount = 0;
|
||||
for (PayPostcodeGroup group : groups)
|
||||
{
|
||||
group.setCreateBy(username);
|
||||
if ("FEE".equals(group.getConfigKind()))
|
||||
{
|
||||
feeCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
zoneCount++;
|
||||
}
|
||||
payPostcodeMapper.insertGroup(group);
|
||||
List<PayPostcodeLine> lines = group.getLines();
|
||||
for (PayPostcodeLine line : lines)
|
||||
{
|
||||
line.setGroupId(group.getGroupId());
|
||||
}
|
||||
for (int i = 0; i < lines.size(); i += BATCH)
|
||||
{
|
||||
payPostcodeMapper.insertLines(new ArrayList<>(lines.subList(i, Math.min(i + BATCH, lines.size()))));
|
||||
}
|
||||
lineCount += lines.size();
|
||||
}
|
||||
PayPostcodeImportResult result = new PayPostcodeImportResult();
|
||||
result.setSourceFile(groups.get(0).getSourceFile());
|
||||
result.setGroupCount(groups.size());
|
||||
result.setFeeCount(feeCount);
|
||||
result.setZoneCount(zoneCount);
|
||||
result.setLineCount(lineCount);
|
||||
result.setMessage("已导入 " + groups.size() + " 条配置,其中附加费 " + feeCount + " 条、邮编分区 " + zoneCount + " 条,邮编 " + lineCount + " 行");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int deleteByIds(Long[] groupIds)
|
||||
{
|
||||
if (groupIds == null || groupIds.length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
List<Long> ids = new ArrayList<>();
|
||||
for (Long id : groupIds)
|
||||
{
|
||||
if (id != null)
|
||||
{
|
||||
ids.add(id);
|
||||
}
|
||||
}
|
||||
if (ids.isEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
payPostcodeMapper.deleteLines(ids);
|
||||
return payPostcodeMapper.deleteGroups(ids);
|
||||
}
|
||||
|
||||
private static String trimToEmpty(String value)
|
||||
{
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static String normalizeCoverage(String value)
|
||||
{
|
||||
String coverage = trimToEmpty(value).toUpperCase(java.util.Locale.ROOT);
|
||||
if (coverage.isEmpty() || "REACH".equals(coverage) || "UNREACH".equals(coverage) || "REMOTE".equals(coverage))
|
||||
{
|
||||
return coverage;
|
||||
}
|
||||
throw new ServiceException("邮编属性只能是可达、不可达或偏远");
|
||||
}
|
||||
}
|
||||
+1077
File diff suppressed because it is too large
Load Diff
+4
@@ -107,6 +107,10 @@ public class PrcProfitReportServiceImpl implements IPrcProfitReportService
|
||||
{
|
||||
return;
|
||||
}
|
||||
if ("COMMISSION_MISSING".equals(query.getDetailType()))
|
||||
{
|
||||
throw new ServiceException("提成功能已暂停使用");
|
||||
}
|
||||
query.setOrderNos(splitFilterValues(query.getOrderNo(), "订单号"));
|
||||
query.setCustomerCodes(splitFilterValues(query.getCustomerCode(), "客户代码"));
|
||||
query.setCustomerNames(splitFilterValues(query.getCustomerName(), "客户名称"));
|
||||
|
||||
@@ -85,6 +85,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectServiceChannelByName" parameterType="String" resultMap="ServiceChannelResult">
|
||||
<include refid="selectChannelVo"/>
|
||||
where channel_name = #{channelName}
|
||||
limit 2
|
||||
</select>
|
||||
|
||||
<select id="countProductChannelByCode" parameterType="String" resultType="int">
|
||||
select count(1) from srv_product_channel where channel_code = #{channelCode}
|
||||
</select>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.price.mapper.ChannelProductMapMapper">
|
||||
|
||||
<select id="selectByChannelAndType" resultType="com.bms.price.domain.ChannelProductMap">
|
||||
select id, channel_id as channelId, product_id as productId, price_set_id as priceSetId,
|
||||
pay_type as payType, create_by as createBy, create_time as createTime,
|
||||
update_by as updateBy, update_time as updateTime
|
||||
from srv_channel_product_map
|
||||
where channel_id = #{channelId} and pay_type = #{payType}
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<insert id="upsert">
|
||||
insert into srv_channel_product_map (
|
||||
channel_id, product_id, price_set_id, pay_type, create_by, create_time, update_by, update_time
|
||||
) values (
|
||||
#{channelId}, #{productId}, #{priceSetId}, #{payType}, #{createBy}, sysdate(), #{updateBy}, sysdate()
|
||||
)
|
||||
on duplicate key update
|
||||
product_id = values(product_id),
|
||||
price_set_id = values(price_set_id),
|
||||
update_by = values(update_by),
|
||||
update_time = sysdate()
|
||||
</insert>
|
||||
|
||||
<delete id="deleteByChannelAndType">
|
||||
delete from srv_channel_product_map
|
||||
where channel_id = #{channelId} and pay_type = #{payType}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByProductId">
|
||||
delete from srv_channel_product_map where product_id = #{productId}
|
||||
</delete>
|
||||
</mapper>
|
||||
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.price.mapper.PayBillMapper">
|
||||
|
||||
<select id="selectPayBillList" resultType="com.bms.price.domain.PayBill">
|
||||
select bill_id as billId, bill_no as billNo, supplier_id as supplierId, supplier_name as supplierName,
|
||||
currency, source_type as sourceType, period_start as periodStart, period_end as periodEnd,
|
||||
order_count as orderCount, total_amount as totalAmount, status, void_reason as voidReason,
|
||||
void_by as voidBy, void_time as voidTime, create_by as createBy, create_time as createTime, remark
|
||||
from pay_bill
|
||||
<where>
|
||||
<if test="billNo != null and billNo != ''">and bill_no like concat('%', #{billNo}, '%')</if>
|
||||
<if test="supplierName != null and supplierName != ''">and supplier_name like concat('%', #{supplierName}, '%')</if>
|
||||
<if test="status != null and status != ''">and status = #{status}</if>
|
||||
<if test="currency != null and currency != ''">and currency = #{currency}</if>
|
||||
</where>
|
||||
order by bill_id desc
|
||||
</select>
|
||||
|
||||
<select id="selectPayBillById" resultType="com.bms.price.domain.PayBill">
|
||||
select bill_id as billId, bill_no as billNo, supplier_id as supplierId, supplier_name as supplierName,
|
||||
currency, source_type as sourceType, period_start as periodStart, period_end as periodEnd,
|
||||
order_count as orderCount, total_amount as totalAmount, status, void_reason as voidReason,
|
||||
void_by as voidBy, void_time as voidTime, create_by as createBy, create_time as createTime, remark
|
||||
from pay_bill where bill_id = #{billId}
|
||||
</select>
|
||||
|
||||
<select id="selectLines" resultType="com.bms.price.domain.PayBillLine">
|
||||
select line_id as lineId, bill_id as billId, order_bill_id as orderBillId, order_no as orderNo,
|
||||
lading_no as ladingNo, lading_fee_id as ladingFeeId, fee_name as feeName, amount, currency,
|
||||
price_date as priceDate, version_no as versionNo, remote_flag as remoteFlag,
|
||||
density_level as densityLevel, line_source as lineSource
|
||||
from pay_bill_line where bill_id = #{billId} order by line_id
|
||||
</select>
|
||||
|
||||
<select id="selectLogs" resultType="com.bms.price.domain.PayBillLog">
|
||||
select log_id as logId, bill_id as billId, operation_type as operationType, from_status as fromStatus,
|
||||
to_status as toStatus, operation_desc as operationDesc, operator_name as operatorName, create_time as createTime
|
||||
from pay_bill_log where bill_id = #{billId} order by log_id
|
||||
</select>
|
||||
|
||||
<select id="selectCandidateOrders" resultType="com.bms.price.domain.PayBillOrder">
|
||||
select o.bill_id as billId, o.order_no as orderNo, o.tracking_no as trackingNo, o.biz_date as bizDate,
|
||||
o.pay_total as payTotal, o.currency as currency, o.pieces as pieces, o.charge_weight as chargeWeight,
|
||||
o.product_name as productName, o.country_code as countryCode, o.country_name as countryName,
|
||||
o.dest_postcode as destPostcode, e.air_no_old as ladingNo, e.product_floor as channelName,
|
||||
e.supplier_name as supplierName,
|
||||
(select group_concat(concat(ifnull(nullif(trim(f.fee_name), ''), ifnull(f.fee_type, '费用')), ' ', format(f.amount, 2))
|
||||
order by field(f.fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE', 'REFUND'), f.fee_id separator ';')
|
||||
from prc_pay_bill_fee f where f.bill_id = o.bill_id) as feeText,
|
||||
(select b.bill_no from pay_bill_line l
|
||||
inner join pay_bill b on b.bill_id = l.bill_id
|
||||
where l.order_bill_id = o.bill_id and b.status <> 'VOID'
|
||||
limit 1) as occupiedBillNo
|
||||
from prc_order_bill o
|
||||
left join prc_order_bill_ext e on e.bill_id = o.bill_id
|
||||
<where>
|
||||
<if test="orderNos != null and orderNos.size() > 0">
|
||||
and o.order_no in
|
||||
<foreach collection="orderNos" item="no" open="(" separator="," close=")">#{no}</foreach>
|
||||
</if>
|
||||
<if test="beginDate != null and beginDate != ''">
|
||||
and o.biz_date >= #{beginDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
and o.biz_date < date_add(#{endDate}, interval 1 day)
|
||||
</if>
|
||||
<if test="ladingNos != null and ladingNos.size() > 0">
|
||||
and (
|
||||
e.air_no_old in
|
||||
<foreach collection="ladingNos" item="no" open="(" separator="," close=")">#{no}</foreach>
|
||||
or e.lading_id in (
|
||||
select lading_id from pay_lading where lading_no in
|
||||
<foreach collection="ladingNos" item="no" open="(" separator="," close=")">#{no}</foreach>
|
||||
)
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
order by o.biz_date, o.order_no
|
||||
limit 2000
|
||||
</select>
|
||||
|
||||
<select id="selectOrderFees" resultType="com.bms.price.domain.PrcPayBillFee">
|
||||
select fee_id as feeId, bill_id as billId, fee_name as feeName, amount, currency, fee_source as feeSource
|
||||
from prc_pay_bill_fee
|
||||
where bill_id in
|
||||
<foreach collection="billIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
order by bill_id, fee_id
|
||||
</select>
|
||||
|
||||
<insert id="insertPayBill" useGeneratedKeys="true" keyProperty="billId">
|
||||
insert into pay_bill (
|
||||
bill_no, supplier_id, supplier_name, currency, source_type, period_start, period_end,
|
||||
order_count, total_amount, status, create_by, create_time, remark
|
||||
) values (
|
||||
#{billNo}, #{supplierId}, #{supplierName}, #{currency}, #{sourceType}, #{periodStart}, #{periodEnd},
|
||||
#{orderCount}, #{totalAmount}, 'DRAFT', #{createBy}, sysdate(), #{remark}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updatePayBillStatus">
|
||||
update pay_bill
|
||||
set status = #{status},
|
||||
void_reason = #{voidReason},
|
||||
void_by = #{voidBy},
|
||||
void_time = #{voidTime},
|
||||
update_by = #{updateBy},
|
||||
update_time = sysdate()
|
||||
where bill_id = #{billId}
|
||||
</update>
|
||||
|
||||
<insert id="insertLines">
|
||||
insert into pay_bill_line (
|
||||
bill_id, order_bill_id, order_no, lading_no, lading_fee_id, fee_name, amount, currency,
|
||||
price_date, version_no, remote_flag, density_level, line_source
|
||||
) values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.billId}, #{item.orderBillId}, #{item.orderNo}, #{item.ladingNo}, #{item.ladingFeeId},
|
||||
#{item.feeName}, #{item.amount}, #{item.currency}, #{item.priceDate}, #{item.versionNo},
|
||||
#{item.remoteFlag}, #{item.densityLevel}, #{item.lineSource})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<insert id="insertLog">
|
||||
insert into pay_bill_log (bill_id, operation_type, from_status, to_status, operation_desc, operator_name, create_time)
|
||||
values (#{billId}, #{operationType}, #{fromStatus}, #{toStatus}, #{operationDesc}, #{operatorName}, sysdate())
|
||||
</insert>
|
||||
</mapper>
|
||||
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.price.mapper.PayPostcodeMapper">
|
||||
|
||||
<select id="selectGroupList" parameterType="com.bms.price.domain.PayPostcodeGroup" resultType="com.bms.price.domain.PayPostcodeGroup">
|
||||
select group_id as groupId, source_file as sourceFile, file_md5 as fileMd5, sheet_name as sheetName,
|
||||
block_title as blockTitle, config_kind as configKind, coverage, channel_name as channelName,
|
||||
country_name as countryName, country_code as countryCode,
|
||||
service_type as serviceType, catalog_country as catalogCountry, product_name as productName, quote_sheet as quoteSheet,
|
||||
fee_name as feeName, fee_text as feeText, line_count as lineCount, create_by as createBy, create_time as createTime, remark
|
||||
from pay_postcode_group
|
||||
<where>
|
||||
<if test="sourceFile != null and sourceFile != ''">and source_file like concat('%', #{sourceFile}, '%')</if>
|
||||
<if test="sheetName != null and sheetName != ''">and sheet_name like concat('%', #{sheetName}, '%')</if>
|
||||
<if test="countryName != null and countryName != ''">and (country_name like concat('%', #{countryName}, '%') or country_code like concat('%', #{countryName}, '%') or catalog_country like concat('%', #{countryName}, '%'))</if>
|
||||
<if test="serviceType != null and serviceType != ''">and service_type like concat('%', #{serviceType}, '%')</if>
|
||||
<if test="productName != null and productName != ''">and product_name like concat('%', #{productName}, '%')</if>
|
||||
<if test="quoteSheet != null and quoteSheet != ''">and quote_sheet like concat('%', #{quoteSheet}, '%')</if>
|
||||
<if test="configKind != null and configKind != ''">and config_kind = #{configKind}</if>
|
||||
<if test="coverage != null and coverage != ''">and coverage = #{coverage}</if>
|
||||
<if test="channelName != null and channelName != ''">and channel_name like concat('%', #{channelName}, '%')</if>
|
||||
<if test="feeName != null and feeName != ''">and fee_name like concat('%', #{feeName}, '%')</if>
|
||||
<if test="postcode != null and postcode != ''">
|
||||
and exists (
|
||||
select 1 from pay_postcode_line l
|
||||
where l.group_id = pay_postcode_group.group_id and l.postcode like concat('%', #{postcode}, '%')
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
order by group_id desc
|
||||
</select>
|
||||
|
||||
<select id="selectLineList" parameterType="com.bms.price.domain.PayPostcodeLine" resultType="com.bms.price.domain.PayPostcodeLine">
|
||||
select line_id as lineId, group_id as groupId, country_name as countryName, country_code as countryCode,
|
||||
region_name as regionName, region_name_cn as regionNameCn, city_name as cityName, region_code as regionCode,
|
||||
zone_name as zoneName, postcode, postcode_raw as postcodeRaw, postcode_end as postcodeEnd,
|
||||
extra_text as extraText, sort_no as sortNo
|
||||
from pay_postcode_line
|
||||
<where>
|
||||
<if test="groupId != null">and group_id = #{groupId}</if>
|
||||
<if test="postcode != null and postcode != ''">and postcode like concat('%', #{postcode}, '%')</if>
|
||||
<if test="regionName != null and regionName != ''">and (region_name like concat('%', #{regionName}, '%') or region_name_cn like concat('%', #{regionName}, '%') or zone_name like concat('%', #{regionName}, '%'))</if>
|
||||
</where>
|
||||
order by sort_no asc, line_id asc
|
||||
</select>
|
||||
|
||||
<insert id="insertGroup" parameterType="com.bms.price.domain.PayPostcodeGroup" useGeneratedKeys="true" keyProperty="groupId">
|
||||
insert into pay_postcode_group (
|
||||
source_file, file_md5, sheet_name, block_title, config_kind, coverage, channel_name, country_name, country_code,
|
||||
service_type, catalog_country, product_name, quote_sheet,
|
||||
fee_name, fee_text, line_count, create_by, create_time, remark
|
||||
) values (
|
||||
#{sourceFile}, #{fileMd5}, #{sheetName}, #{blockTitle}, #{configKind}, #{coverage}, #{channelName}, #{countryName}, #{countryCode},
|
||||
#{serviceType}, #{catalogCountry}, #{productName}, #{quoteSheet},
|
||||
#{feeName}, #{feeText}, #{lineCount}, #{createBy}, sysdate(), #{remark}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertLines">
|
||||
insert into pay_postcode_line (
|
||||
group_id, country_name, country_code, region_name, region_name_cn, city_name, region_code,
|
||||
zone_name, postcode, postcode_raw, postcode_end, extra_text, sort_no
|
||||
) values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.groupId}, #{item.countryName}, #{item.countryCode}, #{item.regionName}, #{item.regionNameCn},
|
||||
#{item.cityName}, #{item.regionCode}, #{item.zoneName}, #{item.postcode}, #{item.postcodeRaw},
|
||||
#{item.postcodeEnd}, #{item.extraText}, #{item.sortNo})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="selectGroupById" resultType="com.bms.price.domain.PayPostcodeGroup">
|
||||
select group_id as groupId, source_file as sourceFile, file_md5 as fileMd5, sheet_name as sheetName,
|
||||
block_title as blockTitle, config_kind as configKind, coverage, channel_name as channelName,
|
||||
country_name as countryName, country_code as countryCode,
|
||||
service_type as serviceType, catalog_country as catalogCountry, product_name as productName, quote_sheet as quoteSheet,
|
||||
fee_name as feeName, fee_text as feeText, line_count as lineCount, create_by as createBy, create_time as createTime, remark
|
||||
from pay_postcode_group
|
||||
where group_id = #{groupId}
|
||||
</select>
|
||||
|
||||
<update id="updateGroup" parameterType="com.bms.price.domain.PayPostcodeGroup">
|
||||
update pay_postcode_group
|
||||
set coverage = #{coverage},
|
||||
channel_name = #{channelName},
|
||||
country_name = #{countryName},
|
||||
country_code = #{countryCode},
|
||||
service_type = #{serviceType},
|
||||
catalog_country = #{catalogCountry},
|
||||
product_name = #{productName},
|
||||
quote_sheet = #{quoteSheet},
|
||||
config_kind = #{configKind},
|
||||
fee_name = #{feeName},
|
||||
fee_text = #{feeText},
|
||||
block_title = #{blockTitle},
|
||||
remark = #{remark}
|
||||
where group_id = #{groupId}
|
||||
</update>
|
||||
|
||||
<select id="selectGroupIds" resultType="long">
|
||||
select group_id from pay_postcode_group where file_md5 = #{fileMd5} and sheet_name = #{sheetName}
|
||||
</select>
|
||||
|
||||
<select id="selectGroupsByTokens" resultType="com.bms.price.domain.PayPostcodeGroup">
|
||||
select group_id as groupId, block_title as blockTitle, channel_name as channelName,
|
||||
product_name as productName, quote_sheet as quoteSheet, sheet_name as sheetName,
|
||||
config_kind as configKind, fee_name as feeName, country_name as countryName, country_code as countryCode
|
||||
from pay_postcode_group
|
||||
where
|
||||
<foreach collection="tokens" item="token" separator=" or ">
|
||||
lower(concat_ws(' ', ifnull(channel_name, ''), ifnull(block_title, ''), ifnull(product_name, ''),
|
||||
ifnull(quote_sheet, ''), ifnull(sheet_name, ''))) like concat('%', #{token}, '%')
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<select id="selectRegionKeys" resultType="com.bms.price.domain.PayPostcodeLine">
|
||||
select group_id as groupId, region_name as regionName, zone_name as zoneName
|
||||
from pay_postcode_line
|
||||
where group_id in
|
||||
<foreach collection="groupIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
group by group_id, region_name, zone_name
|
||||
</select>
|
||||
|
||||
<select id="selectLinesByGroups" resultType="com.bms.price.domain.PayPostcodeLine">
|
||||
select line_id as lineId, group_id as groupId, region_name as regionName, zone_name as zoneName,
|
||||
postcode, postcode_end as postcodeEnd
|
||||
from pay_postcode_line
|
||||
where group_id in
|
||||
<foreach collection="groupIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</select>
|
||||
|
||||
<delete id="deleteLines">
|
||||
delete from pay_postcode_line where group_id in
|
||||
<foreach collection="groupIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</delete>
|
||||
|
||||
<delete id="deleteGroups">
|
||||
delete from pay_postcode_group where group_id in
|
||||
<foreach collection="groupIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</delete>
|
||||
</mapper>
|
||||
@@ -0,0 +1,355 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.price.mapper.PayProductMapper">
|
||||
|
||||
<resultMap type="com.bms.price.domain.PayProduct" id="PayProductResult">
|
||||
<id property="productId" column="product_id"/>
|
||||
<result property="productName" column="product_name"/>
|
||||
<result property="productType" column="product_type"/>
|
||||
<result property="dictValue" column="dict_value"/>
|
||||
<result property="originId" column="origin_id"/>
|
||||
<result property="channelId" column="channel_id"/>
|
||||
<result property="versionNo" column="version_no"/>
|
||||
<result property="countryScope" column="country_scope"/>
|
||||
<result property="chargeType" column="charge_type"/>
|
||||
<result property="extraPerKg" column="extra_per_kg"/>
|
||||
<result property="serviceType" column="service_type"/>
|
||||
<result property="classification" column="classification"/>
|
||||
<result property="catalogRemark" column="catalog_remark"/>
|
||||
<result property="updateContent" column="update_content"/>
|
||||
<result property="weightLimit" column="weight_limit"/>
|
||||
<result property="sheetName" column="sheet_name"/>
|
||||
<result property="relatedSheet" column="related_sheet"/>
|
||||
<result property="sourceFile" column="source_file"/>
|
||||
<result property="fileMd5" column="file_md5"/>
|
||||
<result property="supplierName" column="supplier_name"/>
|
||||
<result property="effectiveDate" column="effective_date"/>
|
||||
<result property="currency" column="currency"/>
|
||||
<result property="serviceNote" column="service_note"/>
|
||||
<result property="parseStatus" column="parse_status"/>
|
||||
<result property="weightCount" column="weight_count"/>
|
||||
<result property="surchargeCount" column="surcharge_count"/>
|
||||
<result property="extraCount" column="extra_count"/>
|
||||
<result property="versionProductId" column="version_product_id"/>
|
||||
<result property="termId" column="term_id"/>
|
||||
<result property="termStatus" column="term_status"/>
|
||||
<result property="termStart" column="term_start"/>
|
||||
<result property="termEnd" column="term_end"/>
|
||||
<result property="createBy" column="created_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="updated_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="payProductWhere">
|
||||
<where>
|
||||
<if test="templateOnly != null and templateOnly">
|
||||
and (pay_product.channel_id is null or pay_product.channel_id = '')
|
||||
</if>
|
||||
<if test="currentOnly != null and currentOnly">
|
||||
and pay_product.origin_id is null
|
||||
</if>
|
||||
<if test="productName != null and productName != ''">
|
||||
and pay_product.product_name like concat('%', #{productName}, '%')
|
||||
</if>
|
||||
<if test="countryScope != null and countryScope != ''">
|
||||
and pay_product.country_scope like concat('%', #{countryScope}, '%')
|
||||
</if>
|
||||
<if test="sheetName != null and sheetName != ''">
|
||||
and pay_product.sheet_name like concat('%', #{sheetName}, '%')
|
||||
</if>
|
||||
<if test="sourceFile != null and sourceFile != ''">
|
||||
and pay_product.source_file like concat('%', #{sourceFile}, '%')
|
||||
</if>
|
||||
<if test="supplierName != null and supplierName != ''">
|
||||
and pay_product.supplier_name like concat('%', #{supplierName}, '%')
|
||||
</if>
|
||||
<if test="serviceType != null and serviceType != ''">
|
||||
and pay_product.service_type like concat('%', #{serviceType}, '%')
|
||||
</if>
|
||||
<if test="parseStatus != null and parseStatus != ''">
|
||||
and pay_product.parse_status = #{parseStatus}
|
||||
</if>
|
||||
<if test="effectiveBegin != null and effectiveBegin != ''">
|
||||
and ifnull(t.effective_start, pay_product.effective_date) >= #{effectiveBegin}
|
||||
</if>
|
||||
<if test="effectiveEnd != null and effectiveEnd != ''">
|
||||
and ifnull(t.effective_start, pay_product.effective_date) <= #{effectiveEnd}
|
||||
</if>
|
||||
<if test="surchargeName != null and surchargeName != ''">
|
||||
and exists (
|
||||
select 1 from pay_surcharge s
|
||||
where s.product_id = pay_product.product_id
|
||||
and (s.fee_name like concat('%', #{surchargeName}, '%')
|
||||
or s.charge_text like concat('%', #{surchargeName}, '%')
|
||||
or s.fee_desc like concat('%', #{surchargeName}, '%')
|
||||
or s.period_text like concat('%', #{surchargeName}, '%'))
|
||||
)
|
||||
</if>
|
||||
<if test="weightKeyword != null and weightKeyword != ''">
|
||||
and exists (
|
||||
select 1 from pay_weight_band b
|
||||
where b.product_id = pay_product.product_id
|
||||
and (b.weight_label like concat('%', #{weightKeyword}, '%')
|
||||
or b.charge_name like concat('%', #{weightKeyword}, '%')
|
||||
or b.zone_name like concat('%', #{weightKeyword}, '%')
|
||||
or b.country_name like concat('%', #{weightKeyword}, '%')
|
||||
or b.section_name like concat('%', #{weightKeyword}, '%')
|
||||
or b.volume_text like concat('%', #{weightKeyword}, '%'))
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<select id="selectPayProductList" parameterType="com.bms.price.domain.PayProduct" resultMap="PayProductResult">
|
||||
select pay_product.product_id, pay_product.product_name,
|
||||
ifnull(cur.product_type, pay_product.product_type) as product_type, pay_product.dict_value, pay_product.origin_id,
|
||||
pay_product.channel_id, ifnull(cur.version_no, pay_product.version_no) as version_no,
|
||||
pay_product.country_scope, ifnull(cur.charge_type, pay_product.charge_type) as charge_type,
|
||||
ifnull(cur.extra_per_kg, pay_product.extra_per_kg) as extra_per_kg, pay_product.service_type,
|
||||
pay_product.classification, pay_product.catalog_remark, pay_product.update_content, pay_product.weight_limit,
|
||||
pay_product.sheet_name, pay_product.related_sheet, ifnull(cur.source_file, pay_product.source_file) as source_file,
|
||||
pay_product.file_md5,
|
||||
pay_product.supplier_name, pay_product.effective_date, ifnull(cur.currency, pay_product.currency) as currency,
|
||||
pay_product.service_note,
|
||||
pay_product.parse_status, ifnull(cur.weight_count, pay_product.weight_count) as weight_count,
|
||||
ifnull(cur.surcharge_count, pay_product.surcharge_count) as surcharge_count, pay_product.extra_count,
|
||||
pay_product.created_by, pay_product.create_time, pay_product.updated_by, pay_product.update_time, pay_product.remark,
|
||||
t.product_id as version_product_id,
|
||||
t.term_id, t.status as term_status, t.effective_start as term_start, t.effective_end as term_end
|
||||
from pay_product
|
||||
left join pay_product_term t on t.term_id = (
|
||||
select t2.term_id
|
||||
from pay_product_term t2
|
||||
inner join pay_product v on v.product_id = t2.product_id
|
||||
where (v.channel_id is null or v.channel_id = '')
|
||||
and (v.product_id = pay_product.product_id or v.origin_id = pay_product.product_id)
|
||||
order by case when t2.status = '0' then 0 else 1 end, ifnull(v.version_no, 1) desc, t2.term_id desc
|
||||
limit 1
|
||||
)
|
||||
left join pay_product cur on cur.product_id = t.product_id
|
||||
<include refid="payProductWhere"/>
|
||||
order by ifnull(t.effective_start, pay_product.effective_date) desc, pay_product.product_id desc
|
||||
</select>
|
||||
|
||||
<select id="selectPayProductById" parameterType="long" resultMap="PayProductResult">
|
||||
select product_id, product_name, product_type, dict_value, origin_id, channel_id, version_no, country_scope, charge_type, extra_per_kg, service_type,
|
||||
classification, catalog_remark, update_content, weight_limit, sheet_name, related_sheet, source_file,
|
||||
file_md5, supplier_name, effective_date, currency, service_note, parse_status, weight_count,
|
||||
surcharge_count, extra_count, created_by, create_time, updated_by, update_time, remark
|
||||
from pay_product
|
||||
where product_id = #{productId}
|
||||
</select>
|
||||
|
||||
<select id="selectExistingId" resultType="long">
|
||||
select product_id from pay_product
|
||||
where file_md5 = #{fileMd5} and sheet_name = #{sheetName} and product_name = #{productName}
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertPayProduct" parameterType="com.bms.price.domain.PayProduct" useGeneratedKeys="true" keyProperty="productId">
|
||||
insert into pay_product (
|
||||
product_name, product_type, dict_value, origin_id, channel_id, version_no, country_scope, charge_type, extra_per_kg, service_type, classification,
|
||||
catalog_remark, update_content, weight_limit, sheet_name, related_sheet, source_file, file_md5,
|
||||
supplier_name, effective_date, currency, service_note, parse_status, weight_count, surcharge_count,
|
||||
extra_count, created_by, create_time, remark
|
||||
) values (
|
||||
#{productName}, #{productType}, #{dictValue}, #{originId}, #{channelId}, ifnull(#{versionNo}, 1), #{countryScope}, #{chargeType}, #{extraPerKg},
|
||||
#{serviceType}, #{classification}, #{catalogRemark}, #{updateContent}, #{weightLimit}, #{sheetName},
|
||||
#{relatedSheet}, #{sourceFile}, #{fileMd5}, #{supplierName}, #{effectiveDate}, #{currency},
|
||||
#{serviceNote}, #{parseStatus}, #{weightCount}, #{surchargeCount}, #{extraCount}, #{createBy}, sysdate(),
|
||||
#{remark}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updatePayProduct" parameterType="com.bms.price.domain.PayProduct">
|
||||
update pay_product
|
||||
set product_name = #{productName},
|
||||
product_type = #{productType},
|
||||
dict_value = #{dictValue},
|
||||
origin_id = #{originId},
|
||||
channel_id = #{channelId},
|
||||
version_no = ifnull(#{versionNo}, version_no),
|
||||
country_scope = #{countryScope},
|
||||
charge_type = #{chargeType},
|
||||
extra_per_kg = #{extraPerKg},
|
||||
service_type = #{serviceType},
|
||||
classification = #{classification},
|
||||
catalog_remark = #{catalogRemark},
|
||||
update_content = #{updateContent},
|
||||
weight_limit = #{weightLimit},
|
||||
sheet_name = #{sheetName},
|
||||
related_sheet = #{relatedSheet},
|
||||
source_file = #{sourceFile},
|
||||
file_md5 = #{fileMd5},
|
||||
supplier_name = #{supplierName},
|
||||
effective_date = #{effectiveDate},
|
||||
currency = #{currency},
|
||||
service_note = #{serviceNote},
|
||||
parse_status = #{parseStatus},
|
||||
weight_count = #{weightCount},
|
||||
surcharge_count = #{surchargeCount},
|
||||
extra_count = #{extraCount},
|
||||
updated_by = #{updateBy},
|
||||
update_time = sysdate(),
|
||||
remark = #{remark}
|
||||
where product_id = #{productId}
|
||||
</update>
|
||||
|
||||
<select id="selectTemplateId" resultType="long">
|
||||
select product_id from pay_product
|
||||
where dict_value = #{dictValue}
|
||||
and (channel_id is null or channel_id = '')
|
||||
and origin_id is null
|
||||
order by product_id
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectTemplateOptions" resultMap="PayProductResult">
|
||||
select p.product_id, p.product_name, p.dict_value, p.sheet_name, p.supplier_name, p.effective_date,
|
||||
p.currency, ifnull(cur.version_no, p.version_no) as version_no
|
||||
from pay_product p
|
||||
left join pay_product_term t on t.term_id = (
|
||||
select t2.term_id
|
||||
from pay_product_term t2
|
||||
inner join pay_product v on v.product_id = t2.product_id
|
||||
where (v.channel_id is null or v.channel_id = '')
|
||||
and (v.product_id = p.product_id or v.origin_id = p.product_id)
|
||||
order by case when t2.status = '0' then 0 else 1 end, ifnull(v.version_no, 1) desc, t2.term_id desc
|
||||
limit 1
|
||||
)
|
||||
left join pay_product cur on cur.product_id = t.product_id
|
||||
where (p.channel_id is null or p.channel_id = '')
|
||||
and p.origin_id is null
|
||||
order by p.product_name
|
||||
</select>
|
||||
|
||||
<select id="selectMaxVersion" resultType="int">
|
||||
select ifnull(max(version_no), 1) from pay_product
|
||||
where product_id = #{originId} or origin_id = #{originId}
|
||||
</select>
|
||||
|
||||
<insert id="insertWeightBands">
|
||||
insert into pay_weight_band (
|
||||
product_id, version_no, section_name, country_name, country_code, zone_name, weight_label, weight_min, weight_max,
|
||||
volume_text, charge_name, price, price_unit, sort_no
|
||||
) values
|
||||
<foreach collection="list" item="row" separator=",">
|
||||
(#{row.productId}, ifnull(#{row.versionNo}, 1), #{row.sectionName}, #{row.countryName}, #{row.countryCode}, #{row.zoneName},
|
||||
#{row.weightLabel}, #{row.weightMin}, #{row.weightMax}, #{row.volumeText}, #{row.chargeName}, #{row.price},
|
||||
#{row.priceUnit}, #{row.sortNo})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<insert id="insertSurcharges">
|
||||
insert into pay_surcharge (
|
||||
product_id, version_no, section_name, fee_name, zone_name, charge_text, amount, currency, fee_desc, period_text,
|
||||
effective_start, effective_end, charge_timing, calc_rule, calc_param, sort_no
|
||||
) values
|
||||
<foreach collection="list" item="row" separator=",">
|
||||
(#{row.productId}, ifnull(#{row.versionNo}, 1), #{row.sectionName}, #{row.feeName}, #{row.zoneName}, #{row.chargeText}, #{row.amount},
|
||||
#{row.currency}, #{row.feeDesc}, #{row.periodText}, #{row.effectiveStart}, #{row.effectiveEnd},
|
||||
#{row.chargeTiming}, ifnull(#{row.calcRule}, 'MANUAL'), #{row.calcParam}, #{row.sortNo})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<insert id="insertExtraLines">
|
||||
insert into pay_extra_line (product_id, line_type, line_text, sort_no) values
|
||||
<foreach collection="list" item="row" separator=",">
|
||||
(#{row.productId}, #{row.lineType}, #{row.lineText}, #{row.sortNo})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="selectWeightBands" resultType="com.bms.price.domain.PayWeightBand">
|
||||
select band_id as bandId, product_id as productId, version_no as versionNo, section_name as sectionName, country_name as countryName,
|
||||
country_code as countryCode, zone_name as zoneName, weight_label as weightLabel, weight_min as weightMin,
|
||||
weight_max as weightMax, volume_text as volumeText, charge_name as chargeName, price, price_unit as priceUnit, sort_no as sortNo
|
||||
from pay_weight_band
|
||||
where product_id in
|
||||
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
order by product_id, sort_no, band_id
|
||||
</select>
|
||||
|
||||
<select id="selectSurcharges" resultType="com.bms.price.domain.PaySurcharge">
|
||||
select surcharge_id as surchargeId, product_id as productId, version_no as versionNo, section_name as sectionName, fee_name as feeName,
|
||||
zone_name as zoneName, charge_text as chargeText, amount, currency, fee_desc as feeDesc,
|
||||
period_text as periodText, effective_start as effectiveStart, effective_end as effectiveEnd,
|
||||
charge_timing as chargeTiming, calc_rule as calcRule, calc_param as calcParam, sort_no as sortNo
|
||||
from pay_surcharge
|
||||
where product_id in
|
||||
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
order by product_id, sort_no, surcharge_id
|
||||
</select>
|
||||
|
||||
<select id="selectExtraLines" resultType="com.bms.price.domain.PayExtraLine">
|
||||
select line_id as lineId, product_id as productId, line_type as lineType, line_text as lineText, sort_no as sortNo
|
||||
from pay_extra_line
|
||||
where product_id in
|
||||
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
order by product_id, sort_no, line_id
|
||||
</select>
|
||||
|
||||
<delete id="deleteWeightBands">
|
||||
delete from pay_weight_band where product_id in
|
||||
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</delete>
|
||||
|
||||
<delete id="deleteSurcharges">
|
||||
delete from pay_surcharge where product_id in
|
||||
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</delete>
|
||||
|
||||
<delete id="deleteExtraLines">
|
||||
delete from pay_extra_line where product_id in
|
||||
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</delete>
|
||||
|
||||
<delete id="deletePayProducts">
|
||||
delete from pay_product where product_id in
|
||||
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</delete>
|
||||
|
||||
<select id="selectFamilyIds" resultType="long">
|
||||
select product_id from pay_product
|
||||
where product_id = #{rootId} or origin_id = #{rootId}
|
||||
</select>
|
||||
|
||||
<select id="selectEffectiveVersionId" resultType="long">
|
||||
select v.product_id
|
||||
from pay_product v
|
||||
inner join pay_product_term t on t.product_id = v.product_id
|
||||
where (v.channel_id is null or v.channel_id = '')
|
||||
and (v.product_id = #{stableId} or v.origin_id = #{stableId})
|
||||
and t.status = '0'
|
||||
and (t.effective_start is null or t.effective_start <= date(#{bizDate}))
|
||||
and (t.effective_end is null or t.effective_end >= date(#{bizDate}))
|
||||
order by ifnull(v.version_no, 1) desc, v.product_id desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectInactiveCovering" resultMap="PayProductResult">
|
||||
select v.product_id, v.product_name, t.version_no
|
||||
from pay_product v
|
||||
inner join pay_product_term t on t.product_id = v.product_id
|
||||
where (v.channel_id is null or v.channel_id = '')
|
||||
and (v.product_id = #{stableId} or v.origin_id = #{stableId})
|
||||
and ifnull(t.status, '1') <> '0'
|
||||
and (t.effective_start is null or t.effective_start <= date(#{bizDate}))
|
||||
and (t.effective_end is null or t.effective_end >= date(#{bizDate}))
|
||||
order by ifnull(t.version_no, 1) desc, v.product_id desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectLatestVersionId" resultType="long">
|
||||
select v.product_id
|
||||
from pay_product v
|
||||
where (v.channel_id is null or v.channel_id = '')
|
||||
and (v.product_id = #{stableId} or v.origin_id = #{stableId})
|
||||
order by ifnull(v.version_no, 1) desc, v.product_id desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.price.mapper.PayProductTermMapper">
|
||||
|
||||
<select id="selectByDict" resultType="com.bms.price.domain.PayProductTerm">
|
||||
select t.term_id as termId, t.dict_value as dictValue, t.product_id as productId, t.version_no as versionNo,
|
||||
t.effective_start as effectiveStart, t.effective_end as effectiveEnd, t.status,
|
||||
t.created_by as createBy, t.create_time as createTime, t.remark,
|
||||
p.product_name as productName, p.source_file as sourceFile, p.weight_count as weightCount,
|
||||
p.surcharge_count as surchargeCount, p.currency
|
||||
from pay_product_term t
|
||||
join pay_product p on p.product_id = t.product_id
|
||||
where t.dict_value = #{dictValue}
|
||||
and (p.channel_id is null or p.channel_id = '')
|
||||
order by t.version_no desc, t.term_id desc
|
||||
</select>
|
||||
|
||||
<select id="selectById" resultType="com.bms.price.domain.PayProductTerm">
|
||||
select term_id as termId, dict_value as dictValue, product_id as productId, version_no as versionNo,
|
||||
effective_start as effectiveStart, effective_end as effectiveEnd, status
|
||||
from pay_product_term
|
||||
where term_id = #{termId}
|
||||
</select>
|
||||
|
||||
<select id="selectByProductId" resultType="com.bms.price.domain.PayProductTerm">
|
||||
select term_id as termId, dict_value as dictValue, product_id as productId, version_no as versionNo,
|
||||
effective_start as effectiveStart, effective_end as effectiveEnd, status
|
||||
from pay_product_term
|
||||
where product_id = #{productId}
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertTerm" parameterType="com.bms.price.domain.PayProductTerm" useGeneratedKeys="true" keyProperty="termId">
|
||||
insert into pay_product_term (
|
||||
dict_value, product_id, version_no, effective_start, effective_end, status, created_by, create_time, remark
|
||||
) values (
|
||||
#{dictValue}, #{productId}, ifnull(#{versionNo}, 1), #{effectiveStart}, #{effectiveEnd},
|
||||
ifnull(#{status}, '0'), #{createBy}, sysdate(), #{remark}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updatePeriod">
|
||||
update pay_product_term
|
||||
set effective_start = #{effectiveStart},
|
||||
effective_end = #{effectiveEnd},
|
||||
updated_by = #{updateBy},
|
||||
update_time = sysdate()
|
||||
where term_id = #{termId}
|
||||
</update>
|
||||
|
||||
<update id="updateStatus">
|
||||
update pay_product_term
|
||||
set status = #{status},
|
||||
updated_by = #{updateBy},
|
||||
update_time = sysdate()
|
||||
where term_id = #{termId}
|
||||
</update>
|
||||
|
||||
<update id="closeOpenTerms">
|
||||
update pay_product_term
|
||||
set status = '1',
|
||||
effective_end = case
|
||||
when effective_end is not null and effective_end < #{endDate} then effective_end
|
||||
when effective_start is not null and effective_start > #{endDate} then effective_start
|
||||
else #{endDate}
|
||||
end,
|
||||
updated_by = #{updateBy},
|
||||
update_time = sysdate()
|
||||
where dict_value = #{dictValue}
|
||||
and status = '0'
|
||||
</update>
|
||||
|
||||
<delete id="deleteByProductIds">
|
||||
delete from pay_product_term where product_id in
|
||||
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</delete>
|
||||
|
||||
<update id="updateProductEffectiveDate">
|
||||
update pay_product
|
||||
set effective_date = #{effectiveDate},
|
||||
update_time = sysdate()
|
||||
where product_id = #{productId}
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -942,6 +942,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where bill_id = #{billId}
|
||||
</update>
|
||||
|
||||
<update id="updatePayTotal">
|
||||
update prc_order_bill
|
||||
set pay_total = #{payTotal},
|
||||
update_time = sysdate()
|
||||
where bill_id = #{billId}
|
||||
</update>
|
||||
|
||||
<update id="updateOrderBillAmounts">
|
||||
update prc_order_bill
|
||||
set surcharge_amount = #{surchargeAmount},
|
||||
|
||||
@@ -44,6 +44,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</insert>
|
||||
|
||||
<delete id="deleteByBillId" parameterType="String">
|
||||
delete from prc_pay_bill_fee where bill_id = #{billId}
|
||||
delete from prc_pay_bill_fee
|
||||
where bill_id = #{billId}
|
||||
and (fee_source is null or fee_source = '' or fee_source = 'CALC')
|
||||
</delete>
|
||||
</mapper>
|
||||
|
||||
Reference in New Issue
Block a user