修改分区功能
This commit is contained in:
+2
-1
@@ -53,7 +53,8 @@ public class TmsPriceController extends BaseController
|
||||
private IBillSearchStore billSearchStore;
|
||||
|
||||
/**
|
||||
* Quote by customerId + productCode + country + weight.
|
||||
* Quote by customer + product + country + weight (+ destPostcode when country has zones).
|
||||
* Zone resolve: destPostcode -> prc_zone_detail/prc_zone -> bms_product_country.zone_code.
|
||||
* Match: customer package FIXED > DISCOUNT > product default.
|
||||
*/
|
||||
@PostMapping("/quote")
|
||||
|
||||
@@ -117,6 +117,9 @@ price.product.import.noCountry=产品明细sheet无有效国家/重量段数据
|
||||
price.product.import.euEmpty=国家表无欧盟成员(is_eu=1),无法展开「欧盟」
|
||||
price.product.import.success.unmatched=未匹配国家表(country_code 为空):{0}
|
||||
# 报价试算 TmsPriceQuote(对外 TMS API 错误码,保持英文契约)
|
||||
price.quote.postcodeRequired=该国家/产品有分区,请填写目的邮编
|
||||
price.quote.postcodeZoneNotFound=邮编未匹配到分区,请核对目的邮编
|
||||
price.quote.zonePriceNotHit=已匹配分区,但该分区无对应产品重量段价格
|
||||
price.quote.priceNotHit=没有产品/国家/重量段绑定,暂无价格
|
||||
price.quote.freightMissing1=产品默认运费为空
|
||||
price.quote.freightMissing2=产品默认运费或挂号费为空
|
||||
|
||||
@@ -22,6 +22,7 @@ public class PrcQuote extends BaseEntity
|
||||
private String productName;
|
||||
private String countryCode;
|
||||
private String countryName;
|
||||
private String destPostcode;
|
||||
private BigDecimal weight;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date bizDate;
|
||||
|
||||
@@ -30,7 +30,6 @@ public class TmsPriceBillRequest extends TmsPriceQuoteRequest
|
||||
private BigDecimal pkgHeight;
|
||||
private String shipperName;
|
||||
private String consigneeName;
|
||||
private String destPostcode;
|
||||
private String warehouseCode;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date orderTime;
|
||||
|
||||
+5
-2
@@ -19,11 +19,14 @@ public class TmsPriceQuoteRequest implements Serializable
|
||||
@NotBlank(message = "productCode is required")
|
||||
private String productCode;
|
||||
|
||||
/** ISO2;与 countryName 二选一 */
|
||||
/** ISO2; countryName alternative */
|
||||
private String countryCode;
|
||||
/** 国家中文名;与 countryCode 二选一 */
|
||||
/** country display name; countryCode alternative */
|
||||
private String countryName;
|
||||
|
||||
/** Dest postcode; required when product-country has zones */
|
||||
private String destPostcode;
|
||||
|
||||
@NotNull(message = "weight is required")
|
||||
private BigDecimal weight;
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ public class TmsPriceQuoteResult implements Serializable
|
||||
private String productName;
|
||||
private String countryCode;
|
||||
private String countryName;
|
||||
private Long zoneId;
|
||||
private String zoneCode;
|
||||
private String zoneName;
|
||||
private Long productCountryId;
|
||||
private BigDecimal weightMin;
|
||||
private BigDecimal weightMax;
|
||||
|
||||
+11
-2
@@ -42,13 +42,22 @@ public interface BmsProductCountryMapper
|
||||
@Param("effectiveDate") Date effectiveDate);
|
||||
|
||||
/**
|
||||
* Match by product + country + weight + bizDate.
|
||||
* effective_date <= bizDate and (expire_date is null or expire_date > bizDate).
|
||||
* Match by product + country + weight + bizDate (+ optional zoneCode).
|
||||
* When zoneCode is empty, prefer rows without zone.
|
||||
* When zoneCode is set, require matching zone_code.
|
||||
*/
|
||||
public BmsProductCountry selectMatchByProductCountryWeight(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("zoneCode") String zoneCode,
|
||||
@Param("weight") BigDecimal weight,
|
||||
@Param("bizDate") Date bizDate);
|
||||
|
||||
/** Count product-country rows that have a zone for this product+country+bizDate */
|
||||
public int countZonedByProductCountry(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("bizDate") Date bizDate);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.price.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.price.domain.PrcZone;
|
||||
import com.ruoyi.price.domain.PrcZoneDetail;
|
||||
|
||||
public interface PrcZoneDetailMapper
|
||||
@@ -25,4 +26,11 @@ public interface PrcZoneDetailMapper
|
||||
public int deletePrcZoneDetailByZoneId(Long zoneId);
|
||||
|
||||
public int deletePrcZoneDetailByRegionId(Long regionId);
|
||||
|
||||
/**
|
||||
* Resolve zone by country ISO2 + postcode (exact first, then longest prefix).
|
||||
*/
|
||||
public PrcZone selectZoneByCountryPostcode(
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("postcode") String postcode);
|
||||
}
|
||||
|
||||
+116
-3
@@ -28,6 +28,8 @@ import com.ruoyi.price.domain.PrcPriceSetDetail;
|
||||
import com.ruoyi.price.domain.PrcQuote;
|
||||
import com.ruoyi.price.domain.PrcQuoteFee;
|
||||
import com.ruoyi.price.domain.PrcSurchargeRel;
|
||||
import com.ruoyi.price.domain.PrcZone;
|
||||
import com.ruoyi.price.mapper.PrcZoneDetailMapper;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteFeeLine;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
||||
@@ -87,6 +89,9 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
@Autowired
|
||||
private PrcQuoteFeeMapper quoteFeeMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcZoneDetailMapper zoneDetailMapper;
|
||||
|
||||
@Autowired
|
||||
private IBillSearchStore billSearchStore;
|
||||
|
||||
@@ -178,11 +183,55 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
}
|
||||
BigDecimal weight = request.getWeight();
|
||||
|
||||
String destPostcode = emptyToNull(request.getDestPostcode());
|
||||
String zoneCode = null;
|
||||
PrcZone resolvedZone = null;
|
||||
int zonedCount = productCountryMapper.countZonedByProductCountry(
|
||||
productCode, countryCode, countryName, bizDate);
|
||||
if (zonedCount > 0)
|
||||
{
|
||||
if (StringUtils.isEmpty(destPostcode))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.postcodeRequired"));
|
||||
}
|
||||
String iso2 = resolveCountryIso2(productCode, countryCode, countryName);
|
||||
if (StringUtils.isEmpty(iso2))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.countryRequired"));
|
||||
}
|
||||
resolvedZone = zoneDetailMapper.selectZoneByCountryPostcode(iso2, normalizePostcode(destPostcode));
|
||||
if (resolvedZone == null || StringUtils.isEmpty(resolvedZone.getZoneCode()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.postcodeZoneNotFound"));
|
||||
}
|
||||
zoneCode = resolvedZone.getZoneCode();
|
||||
if (countryCode == null)
|
||||
{
|
||||
countryCode = iso2;
|
||||
}
|
||||
}
|
||||
|
||||
BmsProductCountry base = productCountryMapper.selectMatchByProductCountryWeight(
|
||||
productCode, countryCode, countryName, weight, bizDate);
|
||||
productCode, countryCode, countryName, zoneCode, weight, bizDate);
|
||||
if (base == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.priceNotHit"));
|
||||
throw new ServiceException(MessageUtils.message(
|
||||
zonedCount > 0 ? "price.quote.zonePriceNotHit" : "price.quote.priceNotHit"));
|
||||
}
|
||||
if (resolvedZone != null)
|
||||
{
|
||||
if (base.getZoneId() == null)
|
||||
{
|
||||
base.setZoneId(resolvedZone.getZoneId());
|
||||
}
|
||||
if (StringUtils.isEmpty(base.getZoneCode()))
|
||||
{
|
||||
base.setZoneCode(resolvedZone.getZoneCode());
|
||||
}
|
||||
if (StringUtils.isEmpty(base.getZoneName()))
|
||||
{
|
||||
base.setZoneName(resolvedZone.getZoneName());
|
||||
}
|
||||
}
|
||||
|
||||
String productName = resolveProductName(productCode, bizDate);
|
||||
@@ -213,6 +262,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
TmsPriceQuoteResult hit = tryFixed(bind, set, base, productCode, productName, weight);
|
||||
if (hit != null)
|
||||
{
|
||||
stampRequestMeta(request, hit);
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
@@ -221,14 +271,67 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
TmsPriceQuoteResult hit = tryDiscount(bind, set, base, productCode, productName, weight);
|
||||
if (hit != null)
|
||||
{
|
||||
stampRequestMeta(request, hit);
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buildDefault(request.getCustomerId(), base, productCode, productName, weight,
|
||||
TmsPriceQuoteResult fallback = buildDefault(request.getCustomerId(), base, productCode, productName, weight,
|
||||
MessageUtils.message("price.quote.noCustomerPackageOverride"));
|
||||
stampRequestMeta(request, fallback);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private void stampRequestMeta(TmsPriceQuoteRequest request, TmsPriceQuoteResult result)
|
||||
{
|
||||
if (result == null || request == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isEmpty(result.getDestPostcode()))
|
||||
{
|
||||
result.setDestPostcode(emptyToNull(request.getDestPostcode()));
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveCountryIso2(String productCode, String countryCode, String countryName)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(countryCode))
|
||||
{
|
||||
return countryCode.toUpperCase();
|
||||
}
|
||||
BmsProductCountry query = new BmsProductCountry();
|
||||
query.setProductCode(productCode);
|
||||
query.setCountryName(countryName);
|
||||
List<BmsProductCountry> rows = productCountryMapper.selectBmsProductCountryList(query);
|
||||
if (rows == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (BmsProductCountry row : rows)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(row.getCountryCode()))
|
||||
{
|
||||
return row.getCountryCode().toUpperCase();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String normalizePostcode(String postcode)
|
||||
{
|
||||
if (StringUtils.isEmpty(postcode))
|
||||
{
|
||||
return postcode;
|
||||
}
|
||||
String n = postcode.trim().replace(" ", "");
|
||||
if (n.endsWith(".0") && n.matches("\\d+\\.0"))
|
||||
{
|
||||
return n.substring(0, n.length() - 2);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private TmsPriceQuoteResult tryFixed(PrcCustomerPriceSet bind, PrcPriceSet set,
|
||||
@@ -359,6 +462,9 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
r.setProductName(productName);
|
||||
r.setCountryCode(base.getCountryCode());
|
||||
r.setCountryName(base.getCountryName());
|
||||
r.setZoneId(base.getZoneId());
|
||||
r.setZoneCode(base.getZoneCode());
|
||||
r.setZoneName(base.getZoneName());
|
||||
r.setProductCountryId(base.getDetailId());
|
||||
r.setWeightMin(base.getWeightMin());
|
||||
r.setWeightMax(base.getWeightMax());
|
||||
@@ -506,6 +612,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
quote.setProductName(result.getProductName());
|
||||
quote.setCountryCode(result.getCountryCode());
|
||||
quote.setCountryName(result.getCountryName());
|
||||
quote.setDestPostcode(result.getDestPostcode());
|
||||
quote.setWeight(weight);
|
||||
quote.setBizDate(bizDate);
|
||||
quote.setTmsId(request.getTmsId() == null ? null : Long.valueOf(request.getTmsId()));
|
||||
@@ -637,6 +744,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
r.setProductName(quote.getProductName());
|
||||
r.setCountryCode(quote.getCountryCode());
|
||||
r.setCountryName(quote.getCountryName());
|
||||
r.setDestPostcode(quote.getDestPostcode());
|
||||
r.setProductCountryId(quote.getProductCountryId());
|
||||
r.setWeightMin(quote.getWeightMin());
|
||||
r.setWeightMax(quote.getWeightMax());
|
||||
@@ -683,6 +791,10 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
to.setProductName(from.getProductName());
|
||||
to.setCountryCode(from.getCountryCode());
|
||||
to.setCountryName(from.getCountryName());
|
||||
to.setDestPostcode(from.getDestPostcode());
|
||||
to.setZoneId(from.getZoneId());
|
||||
to.setZoneCode(from.getZoneCode());
|
||||
to.setZoneName(from.getZoneName());
|
||||
to.setProductCountryId(from.getProductCountryId());
|
||||
to.setWeightMin(from.getWeightMin());
|
||||
to.setWeightMax(from.getWeightMax());
|
||||
@@ -716,6 +828,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
+ request.getProductCode().trim() + "|"
|
||||
+ nvlStr(countryCode) + "|"
|
||||
+ nvlStr(countryName) + "|"
|
||||
+ nvlStr(emptyToNull(request.getDestPostcode())) + "|"
|
||||
+ weight.toPlainString() + "|"
|
||||
+ biz + "|"
|
||||
+ (request.getTmsId() == null ? "" : request.getTmsId());
|
||||
|
||||
@@ -167,6 +167,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and 1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
<choose>
|
||||
<when test="zoneCode != null and zoneCode != ''">
|
||||
and upper(ifnull(zone_code,'')) = upper(#{zoneCode})
|
||||
</when>
|
||||
<otherwise>
|
||||
and (zone_code is null or zone_code = '')
|
||||
</otherwise>
|
||||
</choose>
|
||||
<if test="bizDate != null">
|
||||
and (effective_date is null or effective_date <= #{bizDate})
|
||||
and (expire_date is null or expire_date > #{bizDate})
|
||||
@@ -174,10 +182,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="bizDate == null">
|
||||
and expire_date is null
|
||||
</if>
|
||||
order by
|
||||
case when zone_code is null or zone_code = '' then 0 else 1 end,
|
||||
effective_date desc, weight_min asc, detail_id asc
|
||||
order by effective_date desc, weight_min asc, detail_id asc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="countZonedByProductCountry" resultType="int">
|
||||
select count(1)
|
||||
from bms_product_country
|
||||
where product_code = #{productCode}
|
||||
and zone_code is not null and zone_code <> ''
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
and upper(country_code) = upper(#{countryCode})
|
||||
</when>
|
||||
<when test="countryName != null and countryName != ''">
|
||||
and country_name = #{countryName}
|
||||
</when>
|
||||
<otherwise>
|
||||
and 1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
<if test="bizDate != null">
|
||||
and (effective_date is null or effective_date <= #{bizDate})
|
||||
and (expire_date is null or expire_date > #{bizDate})
|
||||
</if>
|
||||
<if test="bizDate == null">
|
||||
and expire_date is null
|
||||
</if>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -12,6 +12,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="productName" column="product_name" />
|
||||
<result property="countryCode" column="country_code" />
|
||||
<result property="countryName" column="country_name" />
|
||||
<result property="destPostcode" column="dest_postcode" />
|
||||
<result property="weight" column="weight" />
|
||||
<result property="bizDate" column="biz_date" />
|
||||
<result property="tmsId" column="tms_id" />
|
||||
@@ -46,7 +47,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<sql id="selectPrcQuoteVo">
|
||||
select quote_id, quote_key, customer_id, product_code, product_name,
|
||||
country_code, country_name, weight, biz_date, tms_id,
|
||||
country_code, country_name, dest_postcode, weight, biz_date, tms_id,
|
||||
match_source, match_message, price_set_id, price_set_code, price_set_name,
|
||||
set_type, bind_priority, customer_priceset_id, product_country_id,
|
||||
weight_min, weight_max, weight_label, effective_date, charge_weight,
|
||||
@@ -103,7 +104,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<insert id="insertPrcQuote" parameterType="PrcQuote">
|
||||
insert into prc_quote (
|
||||
quote_id, quote_key, customer_id, product_code, product_name,
|
||||
country_code, country_name, weight, biz_date, tms_id,
|
||||
country_code, country_name, dest_postcode, weight, biz_date, tms_id,
|
||||
match_source, match_message, price_set_id, price_set_code, price_set_name,
|
||||
set_type, bind_priority, customer_priceset_id, product_country_id,
|
||||
weight_min, weight_max, weight_label, effective_date, charge_weight,
|
||||
@@ -112,7 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
create_time
|
||||
) values (
|
||||
#{quoteId}, #{quoteKey}, #{customerId}, #{productCode}, #{productName},
|
||||
#{countryCode}, #{countryName}, #{weight}, #{bizDate}, #{tmsId},
|
||||
#{countryCode}, #{countryName}, #{destPostcode}, #{weight}, #{bizDate}, #{tmsId},
|
||||
#{matchSource}, #{matchMessage}, #{priceSetId}, #{priceSetCode}, #{priceSetName},
|
||||
#{setType}, #{bindPriority}, #{customerPricesetId}, #{productCountryId},
|
||||
#{weightMin}, #{weightMax}, #{weightLabel}, #{effectiveDate}, #{chargeWeight},
|
||||
|
||||
@@ -91,4 +91,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where z.region_id = #{regionId}
|
||||
</delete>
|
||||
|
||||
<select id="selectZoneByCountryPostcode" resultType="com.ruoyi.price.domain.PrcZone">
|
||||
select z.zone_id as zoneId,
|
||||
z.region_id as regionId,
|
||||
z.country_code as countryCode,
|
||||
z.zone_code as zoneCode,
|
||||
z.zone_name as zoneName,
|
||||
z.zone_sort as zoneSort,
|
||||
z.remark as remark,
|
||||
z.create_time as createTime,
|
||||
z.update_time as updateTime
|
||||
from prc_zone_detail d
|
||||
inner join prc_zone z on z.zone_id = d.zone_id
|
||||
where upper(ifnull(z.country_code,'')) = upper(#{countryCode})
|
||||
and (
|
||||
replace(upper(ifnull(d.postcode,'')), ' ', '') = replace(upper(#{postcode}), ' ', '')
|
||||
or replace(upper(#{postcode}), ' ', '') like concat(replace(upper(ifnull(d.postcode,'')), ' ', ''), '%')
|
||||
)
|
||||
order by
|
||||
case when replace(upper(ifnull(d.postcode,'')), ' ', '') = replace(upper(#{postcode}), ' ', '') then 0 else 1 end,
|
||||
char_length(replace(ifnull(d.postcode,''), ' ', '')) desc,
|
||||
d.detail_id asc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Billing quote / order sync: postcode -> zone
|
||||
|
||||
## Rule
|
||||
- Whether a country needs a zone is decided by `bms_product_country` (rows with `zone_code`).
|
||||
- If the product+country has zones: request **must** include `destPostcode`.
|
||||
- Zone is resolved at runtime: `destPostcode` -> `prc_zone_detail` + `prc_zone` -> `zone_code`, then match `bms_product_country`.
|
||||
- Billing tables store **postcode only**, not zone columns:
|
||||
- `prc_quote.dest_postcode`
|
||||
- `prc_order_bill.dest_postcode` (already existed)
|
||||
|
||||
## SQL
|
||||
Run: `bms-back/sql/price/billing_postcode_alter.sql`
|
||||
|
||||
## API fields
|
||||
- Request: `destPostcode` on `/api/tms/price/quote` and `/api/tms/price/bill` (and console `/price/billing/quote`).
|
||||
- Response may include `zoneId` / `zoneCode` / `zoneName` for display only (not persisted on bill/quote tables).
|
||||
|
||||
## Errors (i18n)
|
||||
- `price.quote.postcodeRequired`
|
||||
- `price.quote.postcodeZoneNotFound`
|
||||
- `price.quote.zonePriceNotHit`
|
||||
- `price.quote.priceNotHit`
|
||||
@@ -0,0 +1,28 @@
|
||||
-- prc_quote: add dest_postcode only (zone resolved at runtime by postcode)
|
||||
-- billing tables do NOT store zone_* columns
|
||||
SET NAMES utf8mb4;
|
||||
SET @db := DATABASE();
|
||||
|
||||
SET @has := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='prc_quote' AND COLUMN_NAME='dest_postcode');
|
||||
SET @sql := IF(@has=0, 'ALTER TABLE prc_quote ADD COLUMN dest_postcode VARCHAR(20) DEFAULT NULL COMMENT ''dest postcode'' AFTER country_name', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='prc_quote' AND COLUMN_NAME='zone_id');
|
||||
SET @sql := IF(@has>0, 'ALTER TABLE prc_quote DROP COLUMN zone_id', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @has := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='prc_quote' AND COLUMN_NAME='zone_code');
|
||||
SET @sql := IF(@has>0, 'ALTER TABLE prc_quote DROP COLUMN zone_code', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @has := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='prc_quote' AND COLUMN_NAME='zone_name');
|
||||
SET @sql := IF(@has>0, 'ALTER TABLE prc_quote DROP COLUMN zone_name', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='prc_order_bill' AND COLUMN_NAME='zone_id');
|
||||
SET @sql := IF(@has>0, 'ALTER TABLE prc_order_bill DROP COLUMN zone_id', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @has := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='prc_order_bill' AND COLUMN_NAME='zone_code');
|
||||
SET @sql := IF(@has>0, 'ALTER TABLE prc_order_bill DROP COLUMN zone_code', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @has := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='prc_order_bill' AND COLUMN_NAME='zone_name');
|
||||
SET @sql := IF(@has>0, 'ALTER TABLE prc_order_bill DROP COLUMN zone_name', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
@@ -0,0 +1,89 @@
|
||||
-- Add zone + dest_postcode to prc_quote / zone to prc_order_bill
|
||||
-- Idempotent
|
||||
-- charset: utf8mb4
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET @db := DATABASE();
|
||||
|
||||
-- ========== prc_quote ==========
|
||||
SET @has := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_quote' AND COLUMN_NAME = 'dest_postcode'
|
||||
);
|
||||
SET @sql := IF(@has = 0,
|
||||
'ALTER TABLE prc_quote ADD COLUMN dest_postcode VARCHAR(20) DEFAULT NULL COMMENT ''dest postcode'' AFTER country_name',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_quote' AND COLUMN_NAME = 'zone_id'
|
||||
);
|
||||
SET @sql := IF(@has = 0,
|
||||
'ALTER TABLE prc_quote ADD COLUMN zone_id BIGINT(20) DEFAULT NULL COMMENT ''prc_zone.zone_id'' AFTER dest_postcode',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_quote' AND COLUMN_NAME = 'zone_code'
|
||||
);
|
||||
SET @sql := IF(@has = 0,
|
||||
'ALTER TABLE prc_quote ADD COLUMN zone_code VARCHAR(50) DEFAULT NULL COMMENT ''zone code'' AFTER zone_id',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_quote' AND COLUMN_NAME = 'zone_name'
|
||||
);
|
||||
SET @sql := IF(@has = 0,
|
||||
'ALTER TABLE prc_quote ADD COLUMN zone_name VARCHAR(100) DEFAULT NULL COMMENT ''zone display name'' AFTER zone_code',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_quote' AND INDEX_NAME = 'idx_quote_zone'
|
||||
);
|
||||
SET @sql := IF(@has = 0,
|
||||
'ALTER TABLE prc_quote ADD INDEX idx_quote_zone (zone_id)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ========== prc_order_bill ==========
|
||||
SET @has := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'zone_id'
|
||||
);
|
||||
SET @sql := IF(@has = 0,
|
||||
'ALTER TABLE prc_order_bill ADD COLUMN zone_id BIGINT(20) DEFAULT NULL COMMENT ''prc_zone.zone_id'' AFTER country_name',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'zone_code'
|
||||
);
|
||||
SET @sql := IF(@has = 0,
|
||||
'ALTER TABLE prc_order_bill ADD COLUMN zone_code VARCHAR(50) DEFAULT NULL COMMENT ''zone code'' AFTER zone_id',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'zone_name'
|
||||
);
|
||||
SET @sql := IF(@has = 0,
|
||||
'ALTER TABLE prc_order_bill ADD COLUMN zone_name VARCHAR(100) DEFAULT NULL COMMENT ''zone display name'' AFTER zone_code',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_order_bill' AND INDEX_NAME = 'idx_bill_zone'
|
||||
);
|
||||
SET @sql := IF(@has = 0,
|
||||
'ALTER TABLE prc_order_bill ADD INDEX idx_bill_zone (zone_id)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
@@ -34,6 +34,7 @@
|
||||
placeholder="请先选择产品"
|
||||
style="width: 100%"
|
||||
:disabled="!form.productCode"
|
||||
@change="form.destPostcode = undefined"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in countryOptions"
|
||||
@@ -43,6 +44,15 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="needPostcode" label="目的邮编" prop="destPostcode">
|
||||
<el-input
|
||||
v-model="form.destPostcode"
|
||||
clearable
|
||||
placeholder="该国家有分区,请填写邮编"
|
||||
maxlength="20"
|
||||
/>
|
||||
<div class="form-hint">通过邮编判定分区后再匹配产品国家价</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="计费重量(kg)" prop="weight">
|
||||
<el-input-number
|
||||
v-model="form.weight"
|
||||
@@ -80,6 +90,8 @@
|
||||
<el-descriptions-item label="客户ID">{{ result.customerId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品">{{ result.productName || '-' }} ({{ result.productCode }})</el-descriptions-item>
|
||||
<el-descriptions-item label="国家">{{ result.countryName || '-' }} ({{ result.countryCode }})</el-descriptions-item>
|
||||
<el-descriptions-item label="目的邮编">{{ result.destPostcode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="分区">{{ result.zoneName || result.zoneCode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="计费重量(kg)">{{ result.chargeWeight ?? result.weight }}</el-descriptions-item>
|
||||
<el-descriptions-item label="重量段">{{ result.weightLabel || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="明细生效时间">{{ formatDate(result.effectiveDate) }}</el-descriptions-item>
|
||||
@@ -179,6 +191,8 @@
|
||||
<el-descriptions-item label="客户ID">{{ viewResult.customerId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品">{{ viewResult.productName || '-' }} ({{ viewResult.productCode }})</el-descriptions-item>
|
||||
<el-descriptions-item label="国家">{{ viewResult.countryName || '-' }} ({{ viewResult.countryCode }})</el-descriptions-item>
|
||||
<el-descriptions-item label="目的邮编">{{ viewResult.destPostcode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="分区">{{ viewResult.zoneName || viewResult.zoneCode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="计费重量(kg)">{{ viewResult.chargeWeight ?? viewResult.weight }}</el-descriptions-item>
|
||||
<el-descriptions-item label="重量段">{{ viewResult.weightLabel || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="计价来源">
|
||||
@@ -235,6 +249,7 @@ const historyTableRef = ref()
|
||||
const loading = ref(false)
|
||||
const productOptions = ref([])
|
||||
const countryOptions = ref([])
|
||||
const countryRows = ref([])
|
||||
const result = ref(null)
|
||||
const errorMsg = ref('')
|
||||
const historyOpen = ref(false)
|
||||
@@ -282,6 +297,7 @@ const form = reactive({
|
||||
customerCode: undefined,
|
||||
productCode: undefined,
|
||||
countryCode: undefined,
|
||||
destPostcode: undefined,
|
||||
weight: undefined,
|
||||
bizDate: undefined
|
||||
})
|
||||
@@ -290,6 +306,16 @@ const rules = {
|
||||
customerCode: [{ required: true, message: '请选择客户', trigger: 'change' }],
|
||||
productCode: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
||||
countryCode: [{ required: true, message: '请选择目的国/地区', trigger: 'change' }],
|
||||
destPostcode: [{
|
||||
validator: (_r, val, cb) => {
|
||||
if (needPostcode.value && !(val && String(val).trim())) {
|
||||
cb(new Error('该国家有分区,请填写邮编'))
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}],
|
||||
weight: [{ required: true, message: '请输入计费重量', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
@@ -314,14 +340,26 @@ function countryOptionLabel(item) {
|
||||
return name + ' (' + item.countryCode + ')'
|
||||
}
|
||||
|
||||
const needPostcode = computed(() => {
|
||||
if (!form.countryCode) return false
|
||||
return (countryRows.value || []).some(r =>
|
||||
String(r.countryCode || '').toUpperCase() === String(form.countryCode || '').toUpperCase()
|
||||
&& !!(r.zoneCode || r.zoneId)
|
||||
)
|
||||
})
|
||||
|
||||
function handleProductChange(productCode) {
|
||||
form.countryCode = undefined
|
||||
form.destPostcode = undefined
|
||||
countryOptions.value = []
|
||||
countryRows.value = []
|
||||
if (!productCode) {
|
||||
return
|
||||
}
|
||||
listProductCountries(productCode).then(res => {
|
||||
countryOptions.value = uniqueCountries((res.data || []).filter(item => !item.expireDate))
|
||||
const rows = (res.data || []).filter(item => !item.expireDate)
|
||||
countryRows.value = rows
|
||||
countryOptions.value = uniqueCountries(rows)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -337,6 +375,7 @@ function handleQuote() {
|
||||
customerCode: form.customerCode,
|
||||
productCode: form.productCode,
|
||||
countryCode: form.countryCode,
|
||||
destPostcode: form.destPostcode || undefined,
|
||||
weight: form.weight
|
||||
}
|
||||
if (form.bizDate) {
|
||||
@@ -361,9 +400,11 @@ function resetForm() {
|
||||
form.customerCode = undefined
|
||||
form.productCode = undefined
|
||||
form.countryCode = undefined
|
||||
form.destPostcode = undefined
|
||||
form.weight = undefined
|
||||
form.bizDate = undefined
|
||||
countryOptions.value = []
|
||||
countryRows.value = []
|
||||
result.value = null
|
||||
errorMsg.value = ''
|
||||
proxy.$nextTick(() => {
|
||||
|
||||
Reference in New Issue
Block a user