修改产品价和红线价功能

This commit is contained in:
wuweihua
2026-10-28 22:44:13 +08:00
parent 1d47d5781b
commit 3bb7267b0b
9 changed files with 269 additions and 69 deletions
@@ -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 */
@@ -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<PrcPriceSetVersion> 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<PrcPriceSetVersion> 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<PrcPriceSetVersion> 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<PrcPriceSetVersion> 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<PrcPriceSetVersion> 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);