From 38796d86835227b9191c85a9b746b5f598770d8c Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Tue, 8 Sep 2026 10:51:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=85=AC=E5=85=B1=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=AD=97=E5=85=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/SysDictDataController.java | 11 +++ .../com/ruoyi/price/domain/BmsProduct.java | 6 ++ .../ruoyi/price/domain/BmsProductCountry.java | 5 ++ .../price/mapper/BmsProductCountryMapper.java | 17 +++- .../service/impl/BmsProductServiceImpl.java | 89 ++++++++++++++++--- .../service/impl/PrcPriceSetServiceImpl.java | 4 + .../system/service/ISysDictTypeService.java | 8 ++ .../service/impl/SysDictTypeServiceImpl.java | 23 +++++ .../mapper/price/BmsProductCountryMapper.xml | 57 +++++++++--- .../mapper/price/BmsProductMapper.xml | 16 ++++ .../sql/price/dict_product_effect_status.sql | 20 +++++ bms-back/sql/price/dict_tax_mode.sql | 16 ++++ bms-back/sql/price/product.sql | 7 +- .../price/product_country_expire_alter.sql | 14 +++ bms-front/src/api/system/dict/data.js | 8 ++ bms-front/src/permission.js | 4 + bms-front/src/store/modules/dict.js | 48 ++++++++-- bms-front/src/store/modules/user.js | 2 + bms-front/src/utils/dict.js | 14 ++- bms-front/src/views/data/region/index.vue | 3 +- .../src/views/price/basic/product/index.vue | 83 +++++++++-------- bms-front/src/views/price/billing/quote.vue | 5 +- bms-front/src/views/price/set/create.vue | 2 +- bms-front/src/views/price/set/detail.vue | 2 +- bms-front/src/views/system/dict/index.vue | 5 +- 25 files changed, 389 insertions(+), 80 deletions(-) create mode 100644 bms-back/sql/price/dict_product_effect_status.sql create mode 100644 bms-back/sql/price/dict_tax_mode.sql create mode 100644 bms-back/sql/price/product_country_expire_alter.sql diff --git a/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java index f65492b9..87a13a36 100644 --- a/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java +++ b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java @@ -2,6 +2,7 @@ package com.ruoyi.web.controller.system; import java.util.ArrayList; import java.util.List; +import java.util.Map; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; @@ -59,6 +60,16 @@ public class SysDictDataController extends BaseController util.exportExcel(response, list, "字典数据"); } + /** + * 全量查询正常状态字典数据(按 dictType 分组) + */ + @GetMapping(value = "/all") + public AjaxResult all() + { + Map> data = dictTypeService.selectDictDataAllMap(); + return success(data); + } + /** * 查询字典数据详细 */ diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProduct.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProduct.java index c69585d6..f5abb29f 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProduct.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProduct.java @@ -25,9 +25,15 @@ public class BmsProduct extends BaseEntity private String taxMode; @JsonFormat(pattern = "yyyy-MM-dd") private Date effectiveDate; + /** 0启用(按日期展示已生效/待生效) 1已失效 */ private String status; private String sourceSheet; private Long tmsId; private Integer countryCount; private List countries; + /** + * 查询用:ACTIVE已生效 / PENDING待生效 / EXPIRED已失效 + * (非表字段,由生效日期+status 推导) + */ + private String effectStatus; } diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProductCountry.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProductCountry.java index d764c790..39a62a42 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProductCountry.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProductCountry.java @@ -25,8 +25,13 @@ public class BmsProductCountry implements Serializable private String leadTime; @JsonFormat(pattern = "yyyy-MM-dd") private Date effectiveDate; + /** null=open; closed to next import effective_date */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date expireDate; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createdAt; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updatedAt; + /** query only: true = expire_date is null */ + private Boolean openOnly; } diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/BmsProductCountryMapper.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/BmsProductCountryMapper.java index 58283381..61f2528d 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/BmsProductCountryMapper.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/BmsProductCountryMapper.java @@ -21,9 +21,22 @@ public interface BmsProductCountryMapper public int deleteByProductCodes(String[] productCodes); + /** Max open (expire_date is null) effective_date for product */ + public Date selectMaxOpenEffectiveDate(@Param("productCode") String productCode); + + /** Close open versions: set expire_date = newEffectiveDate where effective_date < newEffectiveDate */ + public int expireOpenByProductCode( + @Param("productCode") String productCode, + @Param("newEffectiveDate") Date newEffectiveDate); + + /** Replace same-day version only */ + public int deleteOpenByProductCodeAndEffectiveDate( + @Param("productCode") String productCode, + @Param("effectiveDate") Date effectiveDate); + /** - * 按产品 + 国家 + 重量落入重量段 + 计数时间过滤,匹配一条默认价 - * 只取 effective_date <= bizDate 的已生效明细,多匹配时按 effective_date desc 取最新。 + * Match by product + country + weight + bizDate. + * effective_date <= bizDate and (expire_date is null or expire_date > bizDate). */ public BmsProductCountry selectMatchByProductCountryWeight( @Param("productCode") String productCode, diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/BmsProductServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/BmsProductServiceImpl.java index e014e462..af3334ed 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/BmsProductServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/BmsProductServiceImpl.java @@ -185,26 +185,21 @@ public class BmsProductServiceImpl implements IBmsProductService { product.setStatus("0"); productMapper.insertBmsProduct(product); - saveCountries(product.getProductCode(), countries); + countryNum += saveCountriesVersioned(product.getProductCode(), countries); successNum++; - countryNum += countries.size(); } else if (updateSupport) { product.setProductId(existing.getProductId()); productMapper.updateBmsProduct(product); - countryMapper.deleteByProductCode(product.getProductCode()); - saveCountries(product.getProductCode(), countries); + countryNum += saveCountriesVersioned(product.getProductCode(), countries); updateNum++; - countryNum += countries.size(); } else if (!countries.isEmpty()) { - // Product already exists and "update" unchecked: still refresh country/fee rows from Excel. - countryMapper.deleteByProductCode(product.getProductCode()); - saveCountries(product.getProductCode(), countries); + // Keep product master; version country rows by effective_date + countryNum += saveCountriesVersioned(product.getProductCode(), countries); updateNum++; - countryNum += countries.size(); } else { @@ -224,6 +219,7 @@ public class BmsProductServiceImpl implements IBmsProductService } String msg = "\u5bfc\u5165\u6210\u529f\uff1a\u65b0\u589e " + successNum + " \u4e2a\u4ea7\u54c1\uff0c\u66f4\u65b0 " + updateNum + " \u4e2a\uff0c\u8df3\u8fc7 " + skipNum + " \u4e2a\uff1b\u5199\u5165\u56fd\u5bb6/\u91cd\u91cf\u6bb5\u660e\u7ec6 " + countryNum + " \u884c\u3002" + + "
\u56fd\u5bb6\u660e\u7ec6\uff1a\u82e5\u65b0\u751f\u6548\u65e5\u665a\u4e8e\u4e0a\u6b21\uff0c\u4fdd\u7559\u5386\u53f2\u7248\u4e14\u4e0a\u6b21\u5931\u6548\u65e5=\u672c\u6b21\u751f\u6548\u65e5\uff1b\u540c\u65e5\u5219\u66ff\u6362\u5f53\u524d\u5f00\u653e\u7248\u3002" + "
\u8303\u56f4\uff1a\u4ea7\u54c1\u76ee\u5f55 + \u91cd\u91cf\u6bb5 + \u8fd0\u8d39/\u6302\u53f7\u8d39 + \u5c3a\u5bf8/\u5c3e\u7a0b/\u65f6\u6548\uff1b\u56fd\u5bb6\u7801\u6765\u81ea bms_region\u3002"; if (!unmatchedCountries.isEmpty()) { @@ -233,11 +229,47 @@ public class BmsProductServiceImpl implements IBmsProductService } } - private void saveCountries(String productCode, List countries) + /** + * Versioned country save: + * - newEff > last open eff: close old (expire_date = newEff), insert new (keep history) + * - newEff equals last open: replace that open version only + * - newEff < last open: reject + */ + private int saveCountriesVersioned(String productCode, List countries) { + if (countries == null || countries.isEmpty()) + { + return 0; + } + Date newEff = resolveImportEffectiveDate(countries); + Date lastOpenEff = countryMapper.selectMaxOpenEffectiveDate(productCode); + + if (lastOpenEff != null && newEff != null && newEff.before(lastOpenEff)) + { + throw new ServiceException("\u751f\u6548\u65e5\u671f " + + formatDate(newEff) + " \u65e9\u4e8e\u5f53\u524d\u5f00\u653e\u7248 " + + formatDate(lastOpenEff) + "\uff0c\u8bf7\u4f7f\u7528\u66f4\u665a\u7684\u751f\u6548\u65e5\u671f\u5bfc\u5165"); + } + + if (lastOpenEff != null && newEff != null && newEff.after(lastOpenEff)) + { + // Previous open version expire_date = this effective_date; keep old rows + countryMapper.expireOpenByProductCode(productCode, newEff); + } + else if (lastOpenEff != null) + { + // Same effective day (or both null): replace current open version only + countryMapper.deleteOpenByProductCodeAndEffectiveDate(productCode, newEff); + } + for (BmsProductCountry c : countries) { c.setProductCode(productCode); + c.setExpireDate(null); + if (c.getEffectiveDate() == null) + { + c.setEffectiveDate(newEff); + } try { countryMapper.insertBmsProductCountry(c); @@ -250,9 +282,46 @@ public class BmsProductServiceImpl implements IBmsProductService throw new ServiceException( "\u8bf7\u5148\u6267\u884c SQL\uff1abms-back/sql/price/product_country_fee_alter.sql\uff08\u7f3a\u5c11\u8fd0\u8d39/\u6302\u53f7\u8d39\u5b57\u6bb5\uff09"); } + if (tip.contains("expire_date") || tip.contains("uk_product_country_weight")) + { + throw new ServiceException( + "\u8bf7\u5148\u6267\u884c SQL\uff1abms-back/sql/price/product_country_expire_alter.sql\uff08\u7f3a\u5c11\u5931\u6548\u65e5\u671f/\u7248\u672c\u552f\u4e00\u952e\uff09"); + } throw e; } } + return countries.size(); + } + + private Date resolveImportEffectiveDate(List countries) + { + Date max = null; + for (BmsProductCountry c : countries) + { + if (c.getEffectiveDate() == null) + { + continue; + } + if (max == null || c.getEffectiveDate().after(max)) + { + max = c.getEffectiveDate(); + } + } + return max; + } + + private String formatDate(Date d) + { + if (d == null) + { + return "-"; + } + return new SimpleDateFormat("yyyy-MM-dd").format(d); + } + + private void saveCountries(String productCode, List countries) + { + saveCountriesVersioned(productCode, countries); } private List parseCatalog(Sheet sheet) diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcPriceSetServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcPriceSetServiceImpl.java index ed0a6736..1c7a711f 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcPriceSetServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcPriceSetServiceImpl.java @@ -671,6 +671,10 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } for (BmsProductCountry row : rows) { + if (row.getExpireDate() != null) + { + continue; + } if (!matchDetailCountry(detail, row)) { continue; diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java index 01c1c1dd..ca67b1a1 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java @@ -1,6 +1,7 @@ package com.ruoyi.system.service; import java.util.List; +import java.util.Map; import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.domain.entity.SysDictType; @@ -34,6 +35,13 @@ public interface ISysDictTypeService */ public List selectDictDataByType(String dictType); + /** + * 查询全部正常状态字典数据(按字典类型分组) + * + * @return key=dictType, value=字典数据列表 + */ + public Map> selectDictDataAllMap(); + /** * 根据字典类型ID查询信息 * diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java index 37928c4d..0abaca34 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java @@ -1,6 +1,7 @@ package com.ruoyi.system.service.impl; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -87,6 +88,28 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService return null; } + /** + * 查询全部正常状态字典数据(按字典类型分组) + */ + @Override + public Map> selectDictDataAllMap() + { + SysDictData query = new SysDictData(); + query.setStatus("0"); + Map> dictDataMap = dictDataMapper.selectDictDataList(query).stream() + .collect(Collectors.groupingBy(SysDictData::getDictType)); + Map> result = new HashMap<>(); + for (Map.Entry> entry : dictDataMap.entrySet()) + { + List sorted = entry.getValue().stream() + .sorted(Comparator.comparing(SysDictData::getDictSort)) + .collect(Collectors.toList()); + result.put(entry.getKey(), sorted); + DictUtils.setDictCache(entry.getKey(), sorted); + } + return result; + } + /** * 根据字典类型ID查询信息 * diff --git a/bms-back/ruoyi-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml b/bms-back/ruoyi-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml index 5f32b61d..ce8d09c3 100644 --- a/bms-back/ruoyi-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml +++ b/bms-back/ruoyi-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml @@ -17,15 +17,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + - + AND product_code = #{productCode} @@ -42,19 +47,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND weight_max = #{weightMax} + + AND effective_date = #{effectiveDate} + + + AND expire_date is null + - order by country_name asc, weight_min asc, detail_id asc + order by effective_date desc, country_name asc, weight_min asc, detail_id asc insert into bms_product_country ( product_code, country_name, country_code, weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time, - effective_date, created_at + effective_date, expire_date, created_at ) values ( #{productCode}, #{countryName}, #{countryCode}, #{weightMin}, #{weightMax}, #{freightPrice}, #{registrationFee}, #{sizeLimit}, #{lastMile}, #{leadTime}, - #{effectiveDate}, sysdate() + #{effectiveDate}, #{expireDate}, sysdate() ) @@ -69,11 +80,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + update bms_product_country + set expire_date = #{newEffectiveDate}, + updated_at = sysdate() + where product_code = #{productCode} + and expire_date is null + and (effective_date is null or effective_date < #{newEffectiveDate}) + + + + delete from bms_product_country + where product_code = #{productCode} + and expire_date is null + and ( + (#{effectiveDate} is null and effective_date is null) + or effective_date = #{effectiveDate} + ) + +