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 e69aa7ea..96255825 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,7 +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. + * 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 */ private String priceBuildMode; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java index 5e66c17b..dc84b7b4 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java @@ -3361,9 +3361,10 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } /** - * Draft only: when priceBuildMode changes (PULL/IMPORT), delete this version's + * Draft only: when priceBuildMode changes (PULL/IMPORT/IMPORT_FRESH), delete this version's * product / detail / SALES surcharge rel rows. Same mode or non-draft: no-op. * IMPORT: after wipe, copy products+details from latest prior version (if any). + * IMPORT_FRESH: wipe only, do not copy prior version (blank Excel build). */ private void clearDraftVersionChildrenIfBuildModeChanged(PrcPriceSetVersion db, PrcPriceSetVersion incoming) { @@ -3387,6 +3388,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } Long psvId = db.getPsvId(); wipeVersionChildren(psvId, db.getStatus()); + // IMPORT inherits prior version; IMPORT_FRESH starts empty for Excel import if ("IMPORT".equalsIgnoreCase(newMode)) { copyLatestPriorVersionChildren(db.getPriceSetId(), psvId); diff --git a/bms-back/sql/bms_init.sql b/bms-back/sql/bms_init.sql index 09709539..cb20c5fc 100644 --- a/bms-back/sql/bms_init.sql +++ b/bms-back/sql/bms_init.sql @@ -1314,7 +1314,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导入', + `price_build_mode` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建价格方式 PULL=拉取产品库 IMPORT=Excel继承上一版 IMPORT_FRESH=Excel重新建价', `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_price_set_version_price_build_mode_alter.sql b/bms-back/sql/price/prc_price_set_version_price_build_mode_alter.sql index 4f3751a7..5f20dac9 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导入' AFTER status; + ADD COLUMN price_build_mode varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建价格方式 PULL=拉取产品库 IMPORT=Excel继承上一版 IMPORT_FRESH=Excel重新建价' AFTER status; diff --git a/bms-front/src/views/price/set/components/create/PriceEditPanel.vue b/bms-front/src/views/price/set/components/create/PriceEditPanel.vue index 3692239d..9cd0f36b 100644 --- a/bms-front/src/views/price/set/components/create/PriceEditPanel.vue +++ b/bms-front/src/views/price/set/components/create/PriceEditPanel.vue @@ -201,6 +201,9 @@ async function finish() { // Backend copies latest prior version products/details into this draft await reloadProductsAfterImportModeSave() proxy.$modal.msgSuccess("建价方式已保存,已带出上一版产品,可导入 Excel 按产品+国家覆盖或新增") + } else if (version.priceBuildMode === 'IMPORT_FRESH') { + savedProductsSnapshot.value = (products.value || []).map(p => ({ ...p })) + proxy.$modal.msgSuccess("建价方式已保存并清空旧明细,可直接导入 Excel 建价(不延用以前的产品价格)") } else { savedProductsSnapshot.value = (products.value || []).map(p => ({ ...p })) proxy.$modal.msgSuccess("建价方式已保存并清空旧明细,可导入 Excel 建价") diff --git a/bms-front/src/views/price/set/components/create/StepDetails.vue b/bms-front/src/views/price/set/components/create/StepDetails.vue index 2be973d5..9a42c765 100644 --- a/bms-front/src/views/price/set/components/create/StepDetails.vue +++ b/bms-front/src/views/price/set/components/create/StepDetails.vue @@ -51,7 +51,7 @@ type="button" class="build-card build-card--pull" :class="{ selected: isPullBuildMode(priceBuildMode) }" - :disabled="buildModeLocked || choosingMode" + :disabled="!canChooseBuildMode" @click="chooseBuildMode('PULL')" >
按产品价建价
@@ -61,11 +61,21 @@ type="button" class="build-card build-card--import" :class="{ selected: isImportBuildMode(priceBuildMode) }" - :disabled="buildModeLocked || !isFixedType(setType) || choosingMode" + :disabled="!canChooseBuildMode || !isFixedType(setType)" @click="chooseBuildMode('IMPORT')" > -
Excel 导入建价
-
上传威狮 B2C 小包价表,直接生成套餐销售价
+
按上一版 Excel 建价
+
延用以前的产品价格,带出上一版后再导入 Excel 按产品+国家覆盖或新增
+ + @@ -207,7 +217,9 @@ import { isBuildModeLockedVersion, isDiscountType, isFixedType, + isExcelBuildMode, isImportBuildMode, + isImportFreshBuildMode, isPullBuildMode, pickLatestPriorVersion, setTypeLabel, @@ -233,7 +245,9 @@ const props = defineProps({ /** Version status: draft can change build mode; started versions lock it */ versionStatus: { type: [String, Number], default: undefined }, /** True when build mode changed locally and not saved yet */ - buildModeDirty: { type: Boolean, default: false } + buildModeDirty: { type: Boolean, default: false }, + /** False while parent still loading package data — avoid first click being wiped by applySetData */ + buildModeReady: { type: Boolean, default: true } }) const emit = defineEmits(['update:psvId', 'update:priceBuildMode', 'update:pefId']) @@ -279,7 +293,7 @@ const regionOptions = ref([]) const countryImportDesc = [ '选择目的国家后上传专线成本价表(含「产品目录」),目录中的产品都会记到该国家。', '按产品+国家覆盖:仅替换已选国家的明细,其它产品/国家保留。', - '邮编、赔偿标准等 sheet 不导入。仅套餐「Excel 导入建价」可用。' + '邮编、赔偿标准等 sheet 不导入。仅套餐 Excel 建价方式可用。' ] const importProgressVisible = ref(false) const importProgressPercent = ref(0) @@ -287,16 +301,19 @@ const importProgressMessage = ref('') const importProgressStatus = ref(undefined) const progressDialogTitle = ref('正在导入价格') const hasBuildMode = computed(() => { - if (isPullBuildMode(props.priceBuildMode) || isImportBuildMode(props.priceBuildMode)) return true + if (isPullBuildMode(props.priceBuildMode) || isExcelBuildMode(props.priceBuildMode)) return true // Legacy / already has products: treat as build mode ready return (props.products || []).some(p => p && p.productCode) }) /** Started (non-draft) versions: show cards but disable switching */ const buildModeLocked = computed(() => isBuildModeLockedVersion(props.versionStatus)) const choosingMode = ref(false) +const canChooseBuildMode = computed(() => + !!props.buildModeReady && !buildModeLocked.value && !choosingMode.value +) const isModifyImport = computed(() => { if (isPullBuildMode(props.priceBuildMode)) return true - if (isImportBuildMode(props.priceBuildMode)) return false + if (isExcelBuildMode(props.priceBuildMode)) return false return (props.products || []).some(p => p && p.productCode) }) const importActionLabel = computed(() => @@ -311,27 +328,36 @@ const importDesc = computed(() => { '仅固定价套餐可导入;未匹配行不会新增明细。' ] } + if (isImportFreshBuildMode(props.priceBuildMode)) { + return [ + '适用威狮B2C小包价表(含「产品目录」)。', + '不延用以前的产品价格;导入按「产品+国家」写入套餐销售价,直接生成产品明细。', + '同一产品下重复国家+重量段将整单失败。仅固定价套餐「Excel 重新建价」可用。' + ] + } return [ '适用威狮B2C小包价表(含「产品目录」)。', - '先带出上一版全部产品;导入按「产品+国家」匹配:命中则覆盖该国家明细,未命中则新增;其它产品/国家保留。', - '同一产品下重复国家+重量段将整单失败。仅固定价套餐「导入 Excel 建价」可用。' + '延用以前的产品价格:先带出上一版全部产品;导入按「产品+国家」匹配:命中则覆盖该国家明细,未命中则新增;其它产品/国家保留。', + '同一产品下重复国家+重量段将整单失败。仅固定价套餐「按上一版 Excel 建价」可用。' ] }) const typeHint = computed(() => { if (isPullBuildMode(props.priceBuildMode)) return '· 按产品价建价' - if (isImportBuildMode(props.priceBuildMode)) return '· Excel 导入' + if (isImportBuildMode(props.priceBuildMode)) return '· 按上一版 Excel 建价' + if (isImportFreshBuildMode(props.priceBuildMode)) return '· Excel 重新建价' if (hasBuildMode.value) return '· 当前:' + setTypeLabel(props.setType) return '· 请选择创建价格方式' }) // PULL: hide pull btn (already pulled), show modify-import. -// IMPORT: show create-import. +// IMPORT / IMPORT_FRESH: show create-import. const showPullAction = computed(() => false) const showImportAction = computed(() => hasBuildMode.value && isFixedType(props.setType)) const emptyTip = computed(() => { if (props.livePersist) return '暂无产品' if (isPullBuildMode(props.priceBuildMode)) return '暂无产品,请先返回上一步重新拉取或等待拉取完成' if (isImportBuildMode(props.priceBuildMode)) return '暂无上一版产品,请点击右上角导入 Excel 建价(按产品+国家覆盖或新增)' + if (isImportFreshBuildMode(props.priceBuildMode)) return '暂无产品,请点击右上角导入 Excel 建价' return '暂无产品,请先选择创建价格方式' }) @@ -467,7 +493,7 @@ const pulling = ref(false) function noop() {} async function chooseBuildMode(mode) { - if (!mode || choosingMode.value || buildModeLocked.value) return + if (!mode || !canChooseBuildMode.value) return if (mode === props.priceBuildMode) return choosingMode.value = true try { @@ -1103,6 +1129,7 @@ defineExpose({ pullAllFromLibrary, clearLocalDraft, loadLatestPriorVersionDraft } .build-card--pull:hover:not(:disabled) { border-color: #67c23a; } .build-card--import:hover:not(:disabled) { border-color: #409eff; } +.build-card--import-fresh:hover:not(:disabled) { border-color: #e6a23c; } .build-card__title { font-size: 16px; font-weight: 600; @@ -1111,6 +1138,7 @@ defineExpose({ pullAllFromLibrary, clearLocalDraft, loadLatestPriorVersionDraft } .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__desc { font-size: 13px; line-height: 1.6; diff --git a/bms-front/src/views/price/set/create.vue b/bms-front/src/views/price/set/create.vue index a8810aa0..df56f312 100644 --- a/bms-front/src/views/price/set/create.vue +++ b/bms-front/src/views/price/set/create.vue @@ -21,6 +21,7 @@ :ref-data="refData" :live-persist="livePersist" :build-mode-dirty="buildModeDirty" + :build-mode-ready="pageReady" :header-title="detailsHeaderTitle" /> @@ -100,6 +101,8 @@ const savedProductsSnapshot = ref([]) const buildModeDirty = computed(() => String(form.version.priceBuildMode || '') !== String(savedPriceBuildMode.value || '') ) +/** Gate build-mode cards until package payload applied (avoids first click being wiped). */ +const pageReady = ref(!route.query.priceSetId) function mapSaveProgressMessage(message) { const m = message || '' @@ -182,6 +185,9 @@ async function finishEdit() { // Backend copied prior version into this draft; refresh product list from current psv await refreshProductsFromCurrentVersion() proxy.$modal.msgSuccess("建价方式已保存,已带出上一版产品") + } else if (form.version.priceBuildMode === 'IMPORT_FRESH') { + savedProductsSnapshot.value = (form.products || []).map(p => ({ ...p })) + proxy.$modal.msgSuccess("建价方式已保存,可直接导入 Excel 建价(不延用以前的产品价格)") } else { savedProductsSnapshot.value = (form.products || []).map(p => ({ ...p })) proxy.$modal.msgSuccess("建价方式已保存") @@ -335,7 +341,7 @@ function onPefIdUpdate(pefId) { } } -/** Step2 chose PULL / IMPORT: edit live-saves mode; create only local + optional preview pull. */ +/** Step2 chose PULL / IMPORT / IMPORT_FRESH: edit live-saves mode; create only local + optional preview pull. */ function onPriceBuildModeUpdate(mode) { if (!mode) return if (isBuildModeLockedVersion(form.version.status)) return @@ -346,7 +352,7 @@ function onPriceBuildModeUpdate(mode) { savedProductsSnapshot.value = (form.products || []).map(p => ({ ...p })) } form.version.priceBuildMode = mode - if (mode !== 'IMPORT') { + if (mode !== 'IMPORT' && mode !== 'IMPORT_FRESH') { draftPefId.value = undefined draftPefIds.value = [] } @@ -403,6 +409,8 @@ function onPriceBuildModeUpdate(mode) { proxy.$modal.msgSuccess("请导入 Excel 建价") } }) + } else if (mode === 'IMPORT_FRESH') { + proxy.$modal.msgSuccess("请直接导入 Excel 建价(不延用以前的产品价格)") } } @@ -448,31 +456,48 @@ function applySetData(data, asCopy, asNewVersion) { const fromData = (data.version && data.version.psvId) ? data.version : null const current = byQuery || fromData || versions[0] || {} if (asCopy) { + const keptMode = form.version.priceBuildMode + const keptProducts = keptMode ? form.products.slice() : [] + const keptDetails = keptMode ? form.details.slice() : [] form.version.psvId = undefined form.version.versionNo = nextVersionNo(versions) form.version.effectiveTime = undefined form.version.expireTime = undefined form.version.status = undefined - form.version.priceBuildMode = undefined + form.version.priceBuildMode = keptMode || undefined draftPefId.value = undefined draftPefIds.value = [] savedPriceBuildMode.value = undefined - form.details = [] - form.products = [] + if (keptMode) { + form.products.splice(0, form.products.length, ...keptProducts) + form.details.splice(0, form.details.length, ...keptDetails) + } else { + form.details = [] + form.products = [] + } } else if (asNewVersion) { // Draft is created on finish only. Resume only when query has psvId (legacy empty draft). + // Keep mode/products if user already chose a build mode before this response landed. + const keptMode = form.version.priceBuildMode + const keptProducts = keptMode ? form.products.slice() : [] + const keptDetails = keptMode ? form.details.slice() : [] const resume = qPsv != null ? byQuery : null form.version.psvId = resume ? resume.psvId : undefined form.version.versionNo = (resume && resume.versionNo) || nextVersionNo(versions) form.version.effectiveTime = undefined form.version.expireTime = undefined form.version.status = (resume && resume.status) || '0' - form.version.priceBuildMode = undefined + form.version.priceBuildMode = keptMode || undefined draftPefId.value = undefined draftPefIds.value = [] savedPriceBuildMode.value = undefined - form.details = [] - form.products = [] + if (keptMode) { + form.products.splice(0, form.products.length, ...keptProducts) + form.details.splice(0, form.details.length, ...keptDetails) + } else { + form.details = [] + form.products = [] + } } else { form.version.psvId = current.psvId form.version.versionNo = current.versionNo || nextVersionNo(versions) @@ -491,8 +516,11 @@ function applySetData(data, asCopy, asNewVersion) { } function loadExisting(id, asCopy, asNewVersion) { - getPriceSet(id).then(res => { + pageReady.value = false + return getPriceSet(id).then(res => { applySetData(res.data || {}, asCopy, asNewVersion) + }).finally(() => { + pageReady.value = true }) } @@ -501,6 +529,8 @@ onMounted(() => { const id = route.query.priceSetId if (id) { loadExisting(id, copyMode.value, newVersionMode.value) + } else { + pageReady.value = true } }) }) diff --git a/bms-front/src/views/price/set/utils/priceSet.js b/bms-front/src/views/price/set/utils/priceSet.js index 7b6ac931..c4c43bc4 100644 --- a/bms-front/src/views/price/set/utils/priceSet.js +++ b/bms-front/src/views/price/set/utils/priceSet.js @@ -12,10 +12,16 @@ export const SET_TYPE = { DISCOUNT: 'DISCOUNT' } -/** Version create-price mode */ +/** + * Version create-price mode + * PULL: from product library + * IMPORT: Excel + preload latest prior version + * IMPORT_FRESH: Excel only, no prior version data + */ export const PRICE_BUILD_MODE = { PULL: 'PULL', - IMPORT: 'IMPORT' + IMPORT: 'IMPORT', + IMPORT_FRESH: 'IMPORT_FRESH' } export function isPullBuildMode(mode) { @@ -26,6 +32,15 @@ export function isImportBuildMode(mode) { return mode === PRICE_BUILD_MODE.IMPORT } +export function isImportFreshBuildMode(mode) { + return mode === PRICE_BUILD_MODE.IMPORT_FRESH +} + +/** Either Excel mode (with or without prior version) */ +export function isExcelBuildMode(mode) { + return isImportBuildMode(mode) || isImportFreshBuildMode(mode) +} + /** 套餐状态枚举:D 待启用 / A 审核中 / E 生效 / R 驳回 / S 已停用 */ export const SET_STATUS = { DRAFT: 'D',