diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java index 96255825..befb7652 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java @@ -29,11 +29,10 @@ public class PrcPriceSetVersion extends BaseEntity /** 0 draft / 3 pending / 1 active / 2 expired */ private String status; /** - * Create-price mode for this version: - * PULL = from product library, - * IMPORT = Excel with prior version preload, - * IMPORT_FRESH = Excel without prior version data. - * Column: price_build_mode + * Create-price mode for this version (column: price_build_mode). + * SALES: PULL = product library; IMPORT = Excel + prior version; IMPORT_FRESH = Excel blank. + * PRODUCT/REDLINE: IMPORT = copy from effect version; IMPORT_FRESH = rebuild (Excel). + * Legacy catalog: COPY → IMPORT, old IMPORT → IMPORT_FRESH (see migrate SQL). */ private String priceBuildMode; /** 0 normal / 1 soft-deleted */ diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/support/CatalogVersionSupport.java b/bms-back/bms-system/src/main/java/com/bms/price/service/support/CatalogVersionSupport.java index f85cffdf..ad65c958 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/support/CatalogVersionSupport.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/support/CatalogVersionSupport.java @@ -102,7 +102,8 @@ public class CatalogVersionSupport nv.setVersionNo("V" + next); nv.setEffectiveTime(effectiveTime); nv.setStatus(STATUS_DRAFT); - nv.setPriceBuildMode("IMPORT"); + // IMPORT = inherit effect version; IMPORT_FRESH = empty rebuild (Excel) + nv.setPriceBuildMode(copyFromLatest ? "IMPORT" : "IMPORT_FRESH"); nv.setCreateBy("system"); versionMapper.insertPrcPriceSetVersion(nv); @@ -122,7 +123,11 @@ public class CatalogVersionSupport return nv; } - /** Activate draft: set EFFECT, chain expire on previous EFFECT versions of same type. */ + /** + * Activate draft/pending catalog version. + * IMPORT_FRESH (align sales): may start inside a prior window; forbid equal to any other + * version's effective/expire of the same price_type; then chain expire times and sync children. + */ public void activateVersion(Long psvId, Date effectiveTime) { PrcPriceSetVersion ver = versionMapper.selectPrcPriceSetVersionById(psvId); @@ -140,39 +145,165 @@ public class CatalogVersionSupport { throw new ServiceException("请选择生效时间"); } - Date prevEff = findPrevEffectiveTime(ver); - if (prevEff != null && eff.before(prevEff)) + boolean importFresh = "IMPORT_FRESH".equalsIgnoreCase(StringUtils.trim(ver.getPriceBuildMode())); + if (importFresh) { - throw new ServiceException("生效时间不能早于上一版本"); + validateImportFreshActivateTime(ver, eff); } - - boolean future = eff.after(new Date()); - if (!future) + else { - PrcPriceSetVersion q = new PrcPriceSetVersion(); - q.setPriceType(ver.getPriceType()); - q.setStatus(STATUS_EFFECT); - List prevList = versionMapper.selectPrcPriceSetVersionList(q); - if (prevList != null) + Date prevEff = findPrevEffectiveTime(ver); + if (prevEff != null && eff.before(prevEff)) { - for (PrcPriceSetVersion prev : prevList) - { - if (prev.getPsvId().equals(psvId)) - { - continue; - } - prev.setExpireTime(eff); - prev.setStatus(STATUS_EXPIRED); - versionMapper.updatePrcPriceSetVersion(prev); - } + throw new ServiceException("生效时间不能早于上一版本"); } } ver.setEffectiveTime(eff); ver.setExpireTime(null); - ver.setStatus(future ? STATUS_PENDING : STATUS_EFFECT); versionMapper.updatePrcPriceSetVersion(ver); - refreshCatalogStatuses(ver.getPriceType()); + // Chain B.expire=A.eff, A.expire=next/B.oldExpire; refresh status + product/detail effect + chainAndRefreshCatalogStatuses(ver.getPriceType()); + } + + /** + * IMPORT_FRESH: may be earlier than prev-by-seq; must not equal any other version's eff/exp + * within the same PRODUCT/REDLINE type. + */ + private void validateImportFreshActivateTime(PrcPriceSetVersion version, Date effectiveTime) + { + if (version == null || effectiveTime == null || StringUtils.isEmpty(version.getPriceType())) + { + return; + } + PrcPriceSetVersion q = new PrcPriceSetVersion(); + q.setPriceType(version.getPriceType()); + List list = versionMapper.selectPrcPriceSetVersionList(q); + if (list == null || list.isEmpty()) + { + return; + } + for (PrcPriceSetVersion v : list) + { + if (v == null) + { + continue; + } + if (version.getPsvId() != null && version.getPsvId().equals(v.getPsvId())) + { + continue; + } + if (sameTime(v.getEffectiveTime(), effectiveTime) || sameTime(v.getExpireTime(), effectiveTime)) + { + throw new ServiceException("生效时间不能与已有版本的生效时间或失效时间相同"); + } + } + } + + /** + * Sort timed versions by effective_time, chain expire_time = next start, then set status + * and sync product/detail effect fields (same idea as sales refreshVersionStatus). + */ + private void chainAndRefreshCatalogStatuses(String priceType) + { + assertCatalogType(priceType); + PrcPriceSetVersion q = new PrcPriceSetVersion(); + q.setPriceType(priceType); + List versions = versionMapper.selectPrcPriceSetVersionList(q); + if (versions == null || versions.isEmpty()) + { + return; + } + versions.sort((a, b) -> { + Date ta = a.getEffectiveTime(); + Date tb = b.getEffectiveTime(); + if (ta == null && tb == null) + { + return Integer.compare(nullToZero(a.getVersionSeq()), nullToZero(b.getVersionSeq())); + } + if (ta == null) + { + return 1; + } + if (tb == null) + { + return -1; + } + int c = ta.compareTo(tb); + if (c != 0) + { + return c; + } + return Integer.compare(nullToZero(a.getVersionSeq()), nullToZero(b.getVersionSeq())); + }); + Date now = new Date(); + Long effectPsvId = null; + for (int i = 0; i < versions.size(); i++) + { + PrcPriceSetVersion v = versions.get(i); + Date nextStart = null; + for (int j = i + 1; j < versions.size(); j++) + { + Date t = versions.get(j).getEffectiveTime(); + if (t != null) + { + nextStart = t; + break; + } + } + v.setExpireTime(nextStart); + if (v.getEffectiveTime() == null) + { + v.setStatus(STATUS_DRAFT); + continue; + } + if (v.getEffectiveTime().getTime() > now.getTime()) + { + v.setStatus(STATUS_PENDING); + continue; + } + if (nextStart != null && nextStart.getTime() <= now.getTime()) + { + v.setStatus(STATUS_EXPIRED); + continue; + } + v.setStatus(STATUS_EFFECT); + effectPsvId = v.getPsvId(); + } + if (effectPsvId != null) + { + for (PrcPriceSetVersion v : versions) + { + if (STATUS_EFFECT.equals(v.getStatus()) + && v.getPsvId() != null + && !v.getPsvId().equals(effectPsvId)) + { + v.setStatus(STATUS_EXPIRED); + } + } + } + for (PrcPriceSetVersion v : versions) + { + PrcPriceSetVersion db = versionMapper.selectPrcPriceSetVersionById(v.getPsvId()); + if (db == null) + { + continue; + } + boolean statusChanged = !String.valueOf(v.getStatus()).equals(String.valueOf(db.getStatus())); + boolean expireChanged = !sameTime(v.getExpireTime(), db.getExpireTime()); + boolean effChanged = !sameTime(v.getEffectiveTime(), db.getEffectiveTime()); + if (statusChanged || expireChanged || effChanged) + { + versionMapper.updatePrcPriceSetVersion(v); + } + } + productMapper.syncEffectByPriceType(priceType); + detailMapper.syncEffectByPriceType(priceType); + } + + private static int nullToZero(Integer v) + { + return v == null ? 0 : v.intValue(); } public List listVersions(String priceType, String status) @@ -345,14 +476,16 @@ public class CatalogVersionSupport /** * Prepare draft for catalog price create page. - * copyFromLatest=true: copy from current EFFECT version when draft is empty. - * copyFromLatest=false: empty draft (Excel import / rebuild). + * copyFromLatest=true → priceBuildMode=IMPORT: copy from current EFFECT version when draft is empty. + * copyFromLatest=false → priceBuildMode=IMPORT_FRESH: empty draft (Excel import / rebuild). + * Legacy values COPY/IMPORT are migrated by SQL to IMPORT/IMPORT_FRESH. */ public PrcPriceSetVersion prepareDraftVersion(String priceType, boolean copyFromLatest) { assertCatalogType(priceType); List drafts = listVersions(priceType, STATUS_DRAFT); PrcPriceSetVersion draft; + String mode = copyFromLatest ? "IMPORT" : "IMPORT_FRESH"; if (drafts.isEmpty()) { if (copyFromLatest) @@ -364,7 +497,7 @@ public class CatalogVersionSupport } } draft = ensureDraftVersion(priceType, null, copyFromLatest); - draft.setPriceBuildMode(copyFromLatest ? "COPY" : "IMPORT"); + draft.setPriceBuildMode(mode); versionMapper.updatePrcPriceSetVersion(draft); return reloadCatalogVersion(draft.getPsvId(), priceType); } @@ -386,12 +519,12 @@ public class CatalogVersionSupport productMapper.updatePriceTypeByPsvId(draft.getPsvId(), priceType); detailMapper.updatePriceTypeByPsvId(draft.getPsvId(), priceType); } - draft.setPriceBuildMode("COPY"); + draft.setPriceBuildMode(mode); versionMapper.updatePrcPriceSetVersion(draft); } else if (count == 0) { - draft.setPriceBuildMode("IMPORT"); + draft.setPriceBuildMode(mode); versionMapper.updatePrcPriceSetVersion(draft); } return reloadCatalogVersion(draft.getPsvId(), priceType); diff --git a/bms-back/sql/bms_init.sql b/bms-back/sql/bms_init.sql index 9d3dbc6f..581cfe62 100644 --- a/bms-back/sql/bms_init.sql +++ b/bms-back/sql/bms_init.sql @@ -1298,7 +1298,7 @@ CREATE TABLE `prc_price_set_version` ( `effective_time` datetime DEFAULT NULL COMMENT '生效时间', `expire_time` datetime DEFAULT NULL COMMENT '失效时间(空=长期;下一版本启动时写入该启动时间)', `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '0 draft 3 pending 1 effect 2 expired', - `price_build_mode` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建价格方式 PULL=拉取产品库 IMPORT=Excel继承上一版 IMPORT_FRESH=Excel重新建价', + `price_build_mode` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建价格方式 SALES:PULL/IMPORT/IMPORT_FRESH; PRODUCT/REDLINE:IMPORT=沿用生效版 IMPORT_FRESH=重新建价', `is_del` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '是否删除 0未删除 1已删除', `created_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', diff --git a/bms-back/sql/price/prc_catalog_price_build_mode_import_fresh.sql b/bms-back/sql/price/prc_catalog_price_build_mode_import_fresh.sql new file mode 100644 index 00000000..bf920d2f --- /dev/null +++ b/bms-back/sql/price/prc_catalog_price_build_mode_import_fresh.sql @@ -0,0 +1,15 @@ +-- Catalog (PRODUCT / REDLINE) price_build_mode rename: +-- COPY → IMPORT (按现有生效版本建价) +-- IMPORT → IMPORT_FRESH (重新建产品价) +-- Order matters: migrate old IMPORT first, then COPY. +SET NAMES utf8mb4; + +UPDATE prc_price_set_version +SET price_build_mode = 'IMPORT_FRESH' +WHERE price_type IN ('PRODUCT', 'REDLINE') + AND UPPER(IFNULL(price_build_mode, '')) = 'IMPORT'; + +UPDATE prc_price_set_version +SET price_build_mode = 'IMPORT' +WHERE price_type IN ('PRODUCT', 'REDLINE') + AND UPPER(IFNULL(price_build_mode, '')) = 'COPY'; diff --git a/bms-back/sql/price/prc_price_set_version_price_build_mode_alter.sql b/bms-back/sql/price/prc_price_set_version_price_build_mode_alter.sql index 5f20dac9..c586dc9a 100644 --- a/bms-back/sql/price/prc_price_set_version_price_build_mode_alter.sql +++ b/bms-back/sql/price/prc_price_set_version_price_build_mode_alter.sql @@ -1,3 +1,3 @@ -- Add create-price mode on package version: PULL / IMPORT ALTER TABLE prc_price_set_version - ADD COLUMN price_build_mode varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建价格方式 PULL=拉取产品库 IMPORT=Excel继承上一版 IMPORT_FRESH=Excel重新建价' AFTER status; + ADD COLUMN price_build_mode varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建价格方式 SALES:PULL/IMPORT/IMPORT_FRESH; PRODUCT/REDLINE:IMPORT=沿用生效版 IMPORT_FRESH=重新建价' AFTER status; diff --git a/bms-front/src/views/price/basic/components/CatalogActivateDialog.vue b/bms-front/src/views/price/basic/components/CatalogActivateDialog.vue index 19054862..c21ffdd9 100644 --- a/bms-front/src/views/price/basic/components/CatalogActivateDialog.vue +++ b/bms-front/src/views/price/basic/components/CatalogActivateDialog.vue @@ -1,10 +1,7 @@ - +