修改产品价和红线价功能
This commit is contained in:
@@ -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 */
|
||||
|
||||
+163
-30
@@ -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);
|
||||
|
||||
@@ -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 '创建时间',
|
||||
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<!-- start catalog version: pick effective hour -->
|
||||
<!-- start catalog version: pick effective hour;IMPORT_FRESH 对齐销售价规则 -->
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="dialogTitle" width="460px" append-to-body @closed="onClosed">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item :label="labNo">
|
||||
<span>{{ form.versionNo || '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="labEff" prop="effectiveTime">
|
||||
<el-date-picker
|
||||
v-model="form.effectiveTime"
|
||||
@@ -17,6 +14,7 @@
|
||||
:disabled-hours="disabledHours"
|
||||
style="width: 100%"
|
||||
@change="onTimeChange"
|
||||
@visible-change="onPickerVisible"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div class="activate-hint">{{ hint }}</div>
|
||||
@@ -28,6 +26,8 @@
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import { isImportFreshBuildMode } from '@/views/price/set/utils/priceSet'
|
||||
|
||||
const props = defineProps({
|
||||
loadVersions: { type: Function, required: true },
|
||||
activateFn: { type: Function, required: true }
|
||||
@@ -39,31 +39,43 @@ const submitting = ref(false)
|
||||
const formRef = ref()
|
||||
const versions = ref([])
|
||||
const prevEff = ref()
|
||||
const form = reactive({ psvId: undefined, versionNo: '', versionSeq: 0, effectiveTime: undefined })
|
||||
const isImportFresh = ref(false)
|
||||
const boundaryTimes = ref([])
|
||||
const form = reactive({ psvId: undefined, versionNo: '', versionSeq: 0, effectiveTime: undefined, priceBuildMode: undefined })
|
||||
const dialogTitle = '启动版本'
|
||||
const labNo = '版本号'
|
||||
const labEff = '生效时间'
|
||||
const ph = '请选择生效时间'
|
||||
const btnCancel = '取消'
|
||||
const btnOk = '确认启动'
|
||||
const beforePrev = '生效时间不能早于上一版本的生效时间'
|
||||
const hitBoundary = '生效时间不能与已有版本的生效时间或失效时间相同'
|
||||
const hint = computed(() => {
|
||||
if (isImportFresh.value) {
|
||||
return '重新建价:生效时间可落在历史版本区间内;落在版本 B 区间时,B 失效时间改为本次启动时间,本版本失效时间取 B 原失效时间,并同步产品/明细。不可与本类型已有版本的生效时间或失效时间相同。'
|
||||
}
|
||||
const base = '若生效时间晚于当前,版本将进入待生效;到时自动生效并使上一版本失效。'
|
||||
return prevEff.value ? (base + '不得早于上一版:' + prevEff.value + '。') : base
|
||||
})
|
||||
const rules = {
|
||||
const rules = computed(() => ({
|
||||
effectiveTime: [
|
||||
{ required: true, message: ph, trigger: 'change' },
|
||||
{
|
||||
validator: (_r, v, cb) => {
|
||||
if (!v || !prevEff.value) return cb()
|
||||
if (toMs(v) < toMs(prevEff.value)) return cb(new Error(beforePrev))
|
||||
if (!v) return cb()
|
||||
if (isImportFresh.value) {
|
||||
const ms = toMs(v)
|
||||
if (boundaryTimes.value.some(b => toMs(b) === ms)) {
|
||||
return cb(new Error(hitBoundary))
|
||||
}
|
||||
return cb()
|
||||
}
|
||||
if (prevEff.value && toMs(v) < toMs(prevEff.value)) return cb(new Error(beforePrev))
|
||||
cb()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
}))
|
||||
function pad2(n) { return String(n).padStart(2, '0') }
|
||||
function formatHour(d) {
|
||||
if (!d || Number.isNaN(d.getTime())) return undefined
|
||||
@@ -93,14 +105,29 @@ function findPrev(row) {
|
||||
}
|
||||
return prev ? normalizeToHour(prev.effectiveTime) : undefined
|
||||
}
|
||||
function pickDefault(prev) {
|
||||
function collectBoundaryTimes(excludePsvId) {
|
||||
const out = []
|
||||
const seen = new Set()
|
||||
for (const v of versions.value || []) {
|
||||
if (!v || v.psvId == null || String(v.psvId) === String(excludePsvId)) continue
|
||||
;[v.effectiveTime, v.expireTime].forEach(t => {
|
||||
const n = t ? normalizeToHour(t) : ''
|
||||
if (!n || seen.has(n)) return
|
||||
seen.add(n)
|
||||
out.push(n)
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
function pickDefault(prev, importFresh) {
|
||||
const d = new Date()
|
||||
d.setHours(9, 0, 0, 0)
|
||||
const now = formatHour(d)
|
||||
if (!prev) return now
|
||||
if (importFresh || !prev) return now
|
||||
return toMs(now) >= toMs(prev) ? now : prev
|
||||
}
|
||||
function disabledDate(date) {
|
||||
if (isImportFresh.value) return false
|
||||
if (!prevEff.value || !date) return false
|
||||
const prevDay = new Date(String(prevEff.value).replace(/-/g, '/'))
|
||||
prevDay.setHours(0, 0, 0, 0)
|
||||
@@ -109,11 +136,25 @@ function disabledDate(date) {
|
||||
return d.getTime() < prevDay.getTime()
|
||||
}
|
||||
function disabledHours() {
|
||||
if (!prevEff.value) return []
|
||||
const prevD = new Date(String(prevEff.value).replace(/-/g, '/'))
|
||||
const curD = form.effectiveTime
|
||||
? new Date(String(normalizeToHour(form.effectiveTime)).replace(/-/g, '/'))
|
||||
: new Date()
|
||||
if (isImportFresh.value) {
|
||||
const hours = new Set()
|
||||
for (const b of boundaryTimes.value) {
|
||||
const bd = new Date(String(b).replace(/-/g, '/'))
|
||||
if (
|
||||
bd.getFullYear() === curD.getFullYear() &&
|
||||
bd.getMonth() === curD.getMonth() &&
|
||||
bd.getDate() === curD.getDate()
|
||||
) {
|
||||
hours.add(bd.getHours())
|
||||
}
|
||||
}
|
||||
return [...hours]
|
||||
}
|
||||
if (!prevEff.value) return []
|
||||
const prevD = new Date(String(prevEff.value).replace(/-/g, '/'))
|
||||
if (prevD.getFullYear() !== curD.getFullYear() || prevD.getMonth() !== curD.getMonth() || prevD.getDate() !== curD.getDate()) return []
|
||||
const hours = []
|
||||
for (let h = 0; h < prevD.getHours(); h++) hours.push(h)
|
||||
@@ -123,6 +164,9 @@ function onTimeChange(val) {
|
||||
form.effectiveTime = normalizeToHour(val)
|
||||
formRef.value && formRef.value.validateField('effectiveTime')
|
||||
}
|
||||
function onPickerVisible(visible) {
|
||||
if (visible && formRef.value) formRef.value.validateField('effectiveTime')
|
||||
}
|
||||
async function open(row) {
|
||||
if (!row || row.psvId == null) return
|
||||
const list = await props.loadVersions()
|
||||
@@ -131,15 +175,21 @@ async function open(row) {
|
||||
form.psvId = found.psvId
|
||||
form.versionNo = found.versionNo || ''
|
||||
form.versionSeq = found.versionSeq
|
||||
form.priceBuildMode = found.priceBuildMode
|
||||
isImportFresh.value = isImportFreshBuildMode(found.priceBuildMode)
|
||||
prevEff.value = findPrev(found)
|
||||
form.effectiveTime = pickDefault(prevEff.value)
|
||||
boundaryTimes.value = isImportFresh.value ? collectBoundaryTimes(found.psvId) : []
|
||||
form.effectiveTime = pickDefault(prevEff.value, isImportFresh.value)
|
||||
visible.value = true
|
||||
}
|
||||
function onClosed() {
|
||||
form.psvId = undefined
|
||||
form.versionNo = ''
|
||||
form.effectiveTime = undefined
|
||||
form.priceBuildMode = undefined
|
||||
prevEff.value = undefined
|
||||
isImportFresh.value = false
|
||||
boundaryTimes.value = []
|
||||
formRef.value && formRef.value.clearValidate()
|
||||
}
|
||||
function submit() {
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
<div class="build-choice__cards">
|
||||
<button
|
||||
type="button"
|
||||
class="build-card build-card--pull"
|
||||
:class="{ selected: buildMode === 'COPY' }"
|
||||
class="build-card build-card--import"
|
||||
:class="{ selected: buildMode === 'IMPORT' }"
|
||||
:disabled="!!psvId || choosing || finishing"
|
||||
@click="chooseMode(true)"
|
||||
>
|
||||
@@ -39,8 +39,8 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="build-card build-card--import"
|
||||
:class="{ selected: buildMode === 'IMPORT' }"
|
||||
class="build-card build-card--import-fresh"
|
||||
:class="{ selected: buildMode === 'IMPORT_FRESH' }"
|
||||
:disabled="!!psvId || choosing || finishing"
|
||||
@click="chooseMode(false)"
|
||||
>
|
||||
@@ -183,7 +183,7 @@ const route = useRoute()
|
||||
const labTip = '创建价格方式'
|
||||
const labModeCopy = '按现有生效版本建价'
|
||||
const labModeCopyDesc = '先本地预览已生效版本产品,点击「完成」时再复制到待生效草稿'
|
||||
const labModeFresh = '重新建产品价'
|
||||
const labModeFresh = '新建产品价'
|
||||
const labModeFreshDesc = "不沿用已生效版本;导入解析后先展示,点「完成」时再写入草稿"
|
||||
const labImport = '导入 Excel 建价'
|
||||
const labImportAll = '全部导入(多国家价表)'
|
||||
@@ -300,9 +300,10 @@ function applyVersion(ver) {
|
||||
}
|
||||
psvId.value = ver.psvId
|
||||
versionNo.value = ver.versionNo || ''
|
||||
// Catalog: IMPORT = 沿用生效版; IMPORT_FRESH = 重新建价. Legacy COPY → IMPORT.
|
||||
const mode = String(ver.priceBuildMode || '').toUpperCase()
|
||||
if (mode === 'COPY') buildMode.value = 'COPY'
|
||||
else if (mode === 'IMPORT' || mode === 'PULL') buildMode.value = 'IMPORT'
|
||||
if (mode === 'IMPORT' || mode === 'COPY') buildMode.value = 'IMPORT'
|
||||
else if (mode === 'IMPORT_FRESH' || mode === 'PULL') buildMode.value = 'IMPORT_FRESH'
|
||||
}
|
||||
|
||||
async function loadProducts() {
|
||||
@@ -329,7 +330,7 @@ async function bootstrap() {
|
||||
psvId.value = Number(qPsv)
|
||||
versionNo.value = (route.query && route.query.versionNo) ? String(route.query.versionNo) : ''
|
||||
editingExisting.value = true
|
||||
if (!buildMode.value) buildMode.value = 'IMPORT'
|
||||
if (!buildMode.value) buildMode.value = 'IMPORT_FRESH'
|
||||
await loadProducts()
|
||||
}
|
||||
} finally {
|
||||
@@ -337,10 +338,10 @@ async function bootstrap() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Mode select: local only, no DB write. */
|
||||
/** Mode select: local only, no DB write. inherit=IMPORT, fresh=IMPORT_FRESH. */
|
||||
async function chooseMode(copyFromLatest) {
|
||||
if (psvId.value || choosing.value || finishing.value) return
|
||||
const next = copyFromLatest ? 'COPY' : 'IMPORT'
|
||||
const next = copyFromLatest ? 'IMPORT' : 'IMPORT_FRESH'
|
||||
if (buildMode.value === next && (copyFromLatest ? rows.value.length : true)) {
|
||||
if (copyFromLatest && rows.value.length) return
|
||||
if (!copyFromLatest && !rows.value.length && !pendingImports.value.length) return
|
||||
@@ -426,7 +427,7 @@ async function persistDraft() {
|
||||
proxy.$modal.msgWarning('请先选择建价方式')
|
||||
return false
|
||||
}
|
||||
const res = await props.prepareFn(buildMode.value === 'COPY')
|
||||
const res = await props.prepareFn(buildMode.value === 'IMPORT')
|
||||
applyVersion(res.data)
|
||||
return !!psvId.value
|
||||
}
|
||||
@@ -682,8 +683,10 @@ onMounted(() => { bootstrap() })
|
||||
.build-card:hover:not(:disabled) { box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06); }
|
||||
.build-card:disabled { opacity: 0.55; cursor: not-allowed; }
|
||||
.build-card__title { font-size: 16px; font-weight: 600; color: #303133; margin-bottom: 10px; }
|
||||
.build-card--pull .build-card__title { color: #67c23a; }
|
||||
.build-card--import .build-card__title { color: #409eff; }
|
||||
.build-card--import-fresh .build-card__title { color: #e6a23c; }
|
||||
.build-card--import:hover:not(:disabled) { border-color: #409eff; }
|
||||
.build-card--import-fresh:hover:not(:disabled) { border-color: #e6a23c; }
|
||||
.build-card__desc { font-size: 13px; line-height: 1.6; color: #909399; }
|
||||
.filters { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
|
||||
.filters__left { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
:disabled="!canChooseBuildMode || !isFixedType(setType)"
|
||||
@click="chooseBuildMode('IMPORT_FRESH')"
|
||||
>
|
||||
<div class="build-card__title">Excel 重新建价</div>
|
||||
<div class="build-card__title">Excel 新建价</div>
|
||||
<div class="build-card__desc">不延用以前的产品价格,不带出上一版,上传 Excel 直接生成套餐销售价</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -338,7 +338,7 @@ const importDesc = computed(() => {
|
||||
return [
|
||||
'适用威狮B2C小包价表(含「产品目录」)。',
|
||||
'不延用以前的产品价格;导入按「产品+国家」写入套餐销售价,直接生成产品明细。',
|
||||
'同一产品下重复国家+重量段将整单失败。仅固定价套餐「Excel 重新建价」可用。'
|
||||
'同一产品下重复国家+重量段将整单失败。仅固定价套餐「Excel 新建价」可用。'
|
||||
]
|
||||
}
|
||||
return [
|
||||
@@ -351,7 +351,7 @@ const importDesc = computed(() => {
|
||||
const typeHint = computed(() => {
|
||||
if (isPullBuildMode(props.priceBuildMode)) return '· 按产品价建价'
|
||||
if (isImportBuildMode(props.priceBuildMode)) return '· 按上一版 Excel 建价'
|
||||
if (isImportFreshBuildMode(props.priceBuildMode)) return '· Excel 重新建价'
|
||||
if (isImportFreshBuildMode(props.priceBuildMode)) return '· Excel 新建价'
|
||||
if (hasBuildMode.value) return '· 当前:' + setTypeLabel(props.setType)
|
||||
return '· 请选择创建价格方式'
|
||||
})
|
||||
|
||||
@@ -235,8 +235,8 @@ const activateEffHitBoundaryMsg = '生效时间不能与已有版本的生效时
|
||||
const activateDialogHint = computed(() => {
|
||||
if (activateIsImportFresh.value) {
|
||||
const base = activateMode.value === 'reschedule'
|
||||
? 'Excel 重新建价:生效时间可落在历史版本区间内,将自动截断该版本失效时间并同步产品/明细。'
|
||||
: 'Excel 重新建价:生效时间可落在历史版本区间内;落在版本 B 区间时,B 失效时间改为本次启动时间,本版本失效时间取 B 原失效时间,并同步双方产品/明细。'
|
||||
? 'Excel 新建价:生效时间可落在历史版本区间内,将自动截断该版本失效时间并同步产品/明细。'
|
||||
: 'Excel 新建价:生效时间可落在历史版本区间内;落在版本 B 区间时,B 失效时间改为本次启动时间,本版本失效时间取 B 原失效时间,并同步双方产品/明细。'
|
||||
return base + '不可与已有版本的生效时间或失效时间相同。'
|
||||
}
|
||||
const base = activateMode.value === 'reschedule'
|
||||
|
||||
Reference in New Issue
Block a user