From 853187ad58645b34cd25d92c208aa149bbf4403d Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Fri, 9 Oct 2026 15:51:21 +0800 Subject: [PATCH] Enhance product import validation by adding checks for duplicate country and weight band entries. Update related error messages in properties file. Introduce new method to delete auto fees while retaining manual entries during rebill operations. Update front-end components to manage build mode changes and ensure proper handling of product lists during price set creation. --- .../main/resources/i18n/messages.properties | 2 + .../price/mapper/PrcIncomeBillFeeMapper.java | 3 + .../service/impl/BmsProductServiceImpl.java | 134 ++++++++++++++++++ .../service/impl/PrcOrderBillServiceImpl.java | 74 +++++++++- .../service/impl/PrcPriceSetServiceImpl.java | 32 +++++ .../mapper/price/PrcIncomeBillFeeMapper.xml | 6 + bms-back/sql/price/prc_price_excel_file.sql | 37 +++++ .../set/components/create/PriceEditPanel.vue | 113 ++++++++++++++- .../set/components/create/StepDetails.vue | 31 ++-- bms-front/src/views/price/set/create.vue | 130 ++++++++++++----- .../src/views/price/set/utils/priceSet.js | 11 ++ 11 files changed, 519 insertions(+), 54 deletions(-) create mode 100644 bms-back/sql/price/prc_price_excel_file.sql diff --git a/bms-back/bms-admin/src/main/resources/i18n/messages.properties b/bms-back/bms-admin/src/main/resources/i18n/messages.properties index 2c3d7804..399cb02d 100644 --- a/bms-back/bms-admin/src/main/resources/i18n/messages.properties +++ b/bms-back/bms-admin/src/main/resources/i18n/messages.properties @@ -128,6 +128,8 @@ price.product.import.success.detail2=范围:产品目录 + 重量段 + 运费/ price.product.import.noSheet=未找到产品明细sheet(需与产品名称一致):{0} price.product.import.noCountry=产品明细sheet无有效国家/重量段数据:{0} price.product.import.euEmpty=国家表无欧盟成员(is_eu=1),无法展开「欧盟」 +price.product.import.dupCountry.item=产品{0}({1})重复国家+重量段:{2} +price.product.import.dupCountry=导入失败:以下产品明细中存在重复的国家+重量段,请合并后重试:{0} price.product.import.success.unmatched=未匹配国家表(country_code 为空):{0} price.product.import.countryRequired=请选择目的国家 price.product.import.countryNotFound=国家不存在或已停用:{0} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcIncomeBillFeeMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcIncomeBillFeeMapper.java index 736c2c8d..a1897aff 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcIncomeBillFeeMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcIncomeBillFeeMapper.java @@ -47,4 +47,7 @@ public interface PrcIncomeBillFeeMapper public int deleteByFeeId(String feeId); public int deleteByBillId(String billId); + + /** Rebill: delete auto fees only; keep manual_flag=1 rows. */ + public int deleteAutoFeesByBillId(String billId); } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java index f3a085d5..9436d5a5 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java @@ -309,6 +309,9 @@ public class BmsProductServiceImpl implements IBmsProductService } List euRegions = loadEuRegions(regionByNameCn); Map zoneByCountryAndCode = loadZoneByCountryAndCode(); + // Pre-check: same product cannot have duplicate country + weight band in Excel + assertImportProductsNoDupCountryWeight(workbook, products, regionByNameCn, euRegions, + zoneByCountryAndCode); int successNum = 0; int skipNum = 0; @@ -460,6 +463,7 @@ public class BmsProductServiceImpl implements IBmsProductService product.setTaxMode(resolveTaxMode(product.getProductName())); } List countries = stampSelectedCountry(block.rows, region, forcedEff); + assertNoDuplicateCountryWeightRows(product, countries); int[] nums = persistImportedProduct(product, countries, operName); transactionManager.commit(tx); successNum += nums[0]; @@ -597,6 +601,7 @@ public class BmsProductServiceImpl implements IBmsProductService { continue; } + assertNoDuplicateCountryWeightRows(product, rows); String code = product.getProductCode().trim().toUpperCase(Locale.ROOT); out.put(code, rows); } @@ -632,6 +637,7 @@ public class BmsProductServiceImpl implements IBmsProductService throw new ServiceException(MessageUtils.message("price.product.import.noCountry", productSheet.getSheetName())); } + assertNoDuplicateCountryWeightRows(product, countries); // UI-selected effective date wins over Excel; previous open product/country rows expire at this date Date newEff = requireImportEffectiveDate(forcedEffectiveDate != null ? forcedEffectiveDate : product.getEffectiveDate()); @@ -1988,6 +1994,134 @@ public class BmsProductServiceImpl implements IBmsProductService } } + /** + * Workbook pre-check for full import: reject before any write when a product sheet + * has duplicate country + weight band (same zone) rows. + */ + private void assertImportProductsNoDupCountryWeight(Workbook workbook, List products, + Map regionByNameCn, List euRegions, + Map zoneByCountryAndCode) + { + if (workbook == null || products == null || products.isEmpty()) + { + return; + } + List tips = new ArrayList(); + Set unmatchedCountries = new HashSet(); + for (BmsProduct product : products) + { + if (product == null || StringUtils.isEmpty(product.getProductCode())) + { + continue; + } + Sheet productSheet = findProductSheet(workbook, product); + if (productSheet == null) + { + continue; + } + List rows = parseProductSheet(productSheet, product, + regionByNameCn, euRegions, zoneByCountryAndCode, unmatchedCountries); + String tip = buildDuplicateCountryWeightTip(product, rows); + if (StringUtils.isNotEmpty(tip)) + { + tips.add(tip); + } + } + if (!tips.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.dupCountry", + String.join("
", tips))); + } + } + + /** Fail when Excel rows for one product contain duplicate country + weight band. */ + private void assertNoDuplicateCountryWeightRows(BmsProduct product, List rows) + { + String tip = buildDuplicateCountryWeightTip(product, rows); + if (StringUtils.isNotEmpty(tip)) + { + throw new ServiceException(tip); + } + } + + private String buildDuplicateCountryWeightTip(BmsProduct product, List rows) + { + List dups = findDuplicateCountryWeightLabels(rows); + if (dups.isEmpty()) + { + return null; + } + String code = product == null || StringUtils.isEmpty(product.getProductCode()) + ? "-" : product.getProductCode(); + String name = product == null || StringUtils.isEmpty(product.getProductName()) + ? "-" : product.getProductName(); + return MessageUtils.message("price.product.import.dupCountry.item", + code, name, String.join("\u3001", dups)); + } + + private List findDuplicateCountryWeightLabels(List rows) + { + List dups = new ArrayList(); + if (rows == null || rows.isEmpty()) + { + return dups; + } + Map counts = new LinkedHashMap(); + Map labels = new LinkedHashMap(); + for (BmsProductCountry c : rows) + { + if (c == null) + { + continue; + } + String key = countryWeightDupKey(c); + Integer n = counts.get(key); + counts.put(key, n == null ? 1 : n.intValue() + 1); + if (!labels.containsKey(key)) + { + labels.put(key, formatCountryWeightTip(c)); + } + } + for (Map.Entry e : counts.entrySet()) + { + if (e.getValue() != null && e.getValue().intValue() > 1) + { + dups.add(labels.get(e.getKey())); + } + } + return dups; + } + + /** Align with uk_product_country_zone_weight_eff (country + zone + weight min/max). */ + private String countryWeightDupKey(BmsProductCountry c) + { + String country = StringUtils.isNotEmpty(c.getCountryCode()) + ? c.getCountryCode().trim().toUpperCase(Locale.ROOT) + : (c.getCountryName() == null ? "" : c.getCountryName().trim()); + String zone = c.getZoneCode() == null ? "" : c.getZoneCode().trim().toUpperCase(Locale.ROOT); + return country + "|" + zone + "|" + decimalKey(c.getWeightMin()) + "|" + decimalKey(c.getWeightMax()); + } + + private String formatCountryWeightTip(BmsProductCountry c) + { + String name = StringUtils.isNotEmpty(c.getCountryName()) ? c.getCountryName() + : (StringUtils.isNotEmpty(c.getCountryCode()) ? c.getCountryCode() : "-"); + if (StringUtils.isNotEmpty(c.getZoneCode())) + { + name = name + "(" + c.getZoneCode() + ")"; + } + return name + " " + decimalKey(c.getWeightMin()) + "~" + decimalKey(c.getWeightMax()); + } + + private String decimalKey(BigDecimal v) + { + if (v == null) + { + return ""; + } + return v.stripTrailingZeros().toPlainString(); + } + /** Stamp product + all country detail rows with the same effective date. */ private void applyForcedEffectiveDate(BmsProduct product, List countries, Date newEff) { diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java index 9ef6a685..09b7d47f 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java @@ -8,8 +8,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Arrays; import java.util.Date; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; @@ -1115,18 +1117,39 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService else { orderBillMapper.updatePrcOrderBill(bill); - incomeBillFeeMapper.deleteByBillId(bill.getBillId()); + // Keep manually added fees (manual_flag=1) across rebill + incomeBillFeeMapper.deleteAutoFeesByBillId(bill.getBillId()); // Payable not in scope yet: skip prc_pay_bill_fee cleanup/write. // payBillFeeMapper.deleteByBillId(bill.getBillId()); } PrcIncomeBill incomeBill = persistIncomeBill(bill, result); List fees = toFeeRows(bill.getBillId(), incomeBill == null ? null : incomeBill.getIncomeBillId(), result); + if (existed != null) + { + fees = filterAutoFeesAgainstManual(bill.getBillId(), fees); + } if (!fees.isEmpty()) { incomeBillFeeMapper.insertBatch(fees); } - orderBillMapper.refreshArBillStatusByBillIds(Arrays.asList(bill.getBillId())); + if (existed != null) + { + // Quote totals were written above; recalc so retained manual fees join income/credit + TmsPriceQuoteResult refreshed = refreshBillIncomeTotals(bill); + if (refreshed != null) + { + result.setSurchargeAmount(refreshed.getSurchargeAmount()); + result.setIncomeTotal(refreshed.getIncomeTotal()); + result.setPayableTotal(refreshed.getPayableTotal()); + result.setCreditArrearsStatus(refreshed.getCreditArrearsStatus()); + result.setFeeDetails(refreshed.getFeeDetails()); + } + } + else + { + orderBillMapper.refreshArBillStatusByBillIds(Arrays.asList(bill.getBillId())); + } // Payable not in scope yet: do not write prc_pay_bill / prc_pay_bill_fee. // PrcPayBill payBill = persistPayBill(bill, result); // List payFees = toPayFeeRows(bill.getBillId(), @@ -1141,6 +1164,53 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService billSearchStore.save(EsLogType.BILL, EsBizLogSupport.billBizId(request), requestJson, toResponseJson(result)); } + /** + * Drop auto fee lines that collide with retained manual rows on uk_bill_fee_item + * (bill_id, fee_type, sc_id). Manual fees win on rebill. + */ + private List filterAutoFeesAgainstManual(String billId, List autoFees) + { + if (autoFees == null || autoFees.isEmpty() || StringUtils.isEmpty(billId)) + { + return autoFees == null ? new ArrayList() : autoFees; + } + List existing = incomeBillFeeMapper.selectByBillId(billId); + if (existing == null || existing.isEmpty()) + { + return autoFees; + } + Set manualKeys = new HashSet(); + for (PrcIncomeBillFee row : existing) + { + if (row != null && "1".equals(row.getManualFlag())) + { + manualKeys.add(feeUniqueKey(row.getFeeType(), row.getScId())); + } + } + if (manualKeys.isEmpty()) + { + return autoFees; + } + List out = new ArrayList(); + for (PrcIncomeBillFee fee : autoFees) + { + if (fee == null) + { + continue; + } + if (!manualKeys.contains(feeUniqueKey(fee.getFeeType(), fee.getScId()))) + { + out.add(fee); + } + } + return out; + } + + private String feeUniqueKey(String feeType, Long scId) + { + return StringUtils.nvl(feeType, "") + "|" + (scId == null ? "0" : String.valueOf(scId)); + } + /** * Occupy/release credit by income total delta (bizType=ORDER). * Missing/disabled customer: skip occupy, return NORMAL. 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 55096802..10649d04 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 @@ -2733,6 +2733,8 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { version.setExpireTime(db.getExpireTime()); } + // Draft + build mode changed: wipe old product/detail/surcharge before mode switch + clearDraftVersionChildrenIfBuildModeChanged(db, version); versionMapper.updatePrcPriceSetVersion(version); } set.setVersion(version); @@ -2749,6 +2751,36 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService refreshVersionStatus(set.getPriceSetId()); } + /** + * Draft only: when priceBuildMode changes (PULL/IMPORT), delete this version's + * product / detail / surcharge rows. Same mode or non-draft: no-op. + */ + private void clearDraftVersionChildrenIfBuildModeChanged(PrcPriceSetVersion db, PrcPriceSetVersion incoming) + { + if (db == null || incoming == null || db.getPsvId() == null) + { + return; + } + if (!VER_DRAFT.equals(db.getStatus())) + { + return; + } + String newMode = StringUtils.trim(incoming.getPriceBuildMode()); + if (StringUtils.isEmpty(newMode)) + { + return; + } + String oldMode = StringUtils.trim(db.getPriceBuildMode()); + if (newMode.equalsIgnoreCase(StringUtils.nvl(oldMode, ""))) + { + return; + } + Long psvId = db.getPsvId(); + versionSurchargeMapper.deleteByPsvId(psvId); + detailMapper.deletePrcPriceSetDetailByPsvId(psvId); + setProductMapper.deleteByPsvId(psvId); + } + /** Prefer same effective_time, else draft, else first version of the set. */ private PrcPriceSetVersion findReusableVersion(Long priceSetId, Date effectiveTime) { diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml index 208c858d..dea6e05d 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml @@ -180,4 +180,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" delete from prc_income_bill_fee where bill_id = #{billId} + + + delete from prc_income_bill_fee + where bill_id = #{billId} + and (manual_flag is null or manual_flag <> '1') + diff --git a/bms-back/sql/price/prc_price_excel_file.sql b/bms-back/sql/price/prc_price_excel_file.sql new file mode 100644 index 00000000..4245c594 --- /dev/null +++ b/bms-back/sql/price/prc_price_excel_file.sql @@ -0,0 +1,37 @@ +-- ------------------------------------------------------------ +-- 导入价表 Excel 附件记录(仅建表/字段,待确认后再接代码) +-- 用途:产品价/红线价/销售价 Excel 导入时落库文件元数据 +-- 关联:product.pef_id、prc_price_set_version.pef_id -> prc_price_excel_file.pef_id +-- ------------------------------------------------------------ + +-- 1) 主表:导入价表 Excel 附件 +CREATE TABLE IF NOT EXISTS prc_price_excel_file ( + pef_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '1 主键', + excel_type varchar(20) NOT NULL COMMENT '2 价表类型 PRODUCT=产品价 REDLINE=红线价 SALES=销售价', + effective_time datetime NOT NULL COMMENT '3 生效时间(导入时选择/解析)', + file_name varchar(255) NOT NULL COMMENT '4 文件原始名称', + file_path varchar(500) NOT NULL COMMENT '5 文件存储路径(相对路径或完整URL)', + file_size bigint(20) DEFAULT 0 COMMENT '6 文件大小(字节)', + content_type varchar(100) DEFAULT '' COMMENT '7 MIME类型,如 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + file_extension varchar(20) DEFAULT '' COMMENT '8 扩展名,如 xlsx/xls', + oss_id varchar(64) DEFAULT NULL COMMENT '9 OSS/对象存储ID(可空)', + sheet_names varchar(1000) DEFAULT NULL COMMENT '10 导入涉及的 sheet 名(多个逗号分隔,可空)', + remark varchar(500) DEFAULT NULL COMMENT '11 备注', + created_by varchar(64) DEFAULT '' COMMENT '12 创建人', + create_time datetime DEFAULT NULL COMMENT '13 创建时间', + updated_by varchar(64) DEFAULT '' COMMENT '14 更新人', + update_time datetime DEFAULT NULL COMMENT '15 更新时间', + PRIMARY KEY (pef_id), + KEY idx_pef_type_eff (excel_type, effective_time), + KEY idx_pef_create_time (create_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='导入价表Excel附件'; + +-- 2) 产品主表:关联导入附件 +ALTER TABLE product + ADD COLUMN pef_id bigint(20) DEFAULT NULL COMMENT '关联导入价表Excel附件 prc_price_excel_file.pef_id' AFTER source_sheet, + ADD KEY idx_product_pef_id (pef_id); + +-- 3) 套餐版本表:关联导入附件(销售价 Excel 建价) +ALTER TABLE prc_price_set_version + ADD COLUMN pef_id bigint(20) DEFAULT NULL COMMENT '关联导入价表Excel附件 prc_price_excel_file.pef_id' AFTER price_build_mode, + ADD KEY idx_psv_pef_id (pef_id); 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 c35f46e2..6dfe8d2c 100644 --- a/bms-front/src/views/price/set/components/create/PriceEditPanel.vue +++ b/bms-front/src/views/price/set/components/create/PriceEditPanel.vue @@ -11,13 +11,14 @@
关闭 - 完成 + 完成
@@ -38,7 +43,8 @@ import { ArrowLeft } from '@element-plus/icons-vue' import StepDetails from './StepDetails.vue' import { usePriceSetRefData } from '../../composables/usePriceSetRefData' -import { getPriceSet } from '@/api/price/set' +import { isBuildModeLockedVersion } from '../../utils/priceSet' +import { getPriceSet, savePriceSetAsync } from '@/api/price/set' const props = defineProps({ priceSetId: { type: [Number, String], default: undefined }, @@ -53,9 +59,18 @@ const refData = usePriceSetRefData() const loading = ref(false) const loaded = ref(false) const setType = ref('FIXED') -const version = reactive({ psvId: undefined, versionNo: 'V1', effectiveTime: undefined, priceBuildMode: undefined }) +const priceSetName = ref('') +const version = reactive({ psvId: undefined, versionNo: 'V1', effectiveTime: undefined, status: undefined, priceBuildMode: undefined }) +const savedPriceBuildMode = ref(undefined) +const saving = ref(false) const details = ref([]) const products = ref([]) +const detailsRef = ref() +/** Snapshot of products under the last saved build mode (restore if switch back before Complete) */ +const savedProductsSnapshot = ref([]) +const buildModeDirty = computed(() => + String(version.priceBuildMode || '') !== String(savedPriceBuildMode.value || '') +) const title = computed(() => '编辑产品价格 · ' + (version.versionNo || '')) @@ -70,6 +85,7 @@ function load() { getPriceSet(id).then(res => { const data = res.data || {} setType.value = data.setType || 'FIXED' + priceSetName.value = data.priceSetName || '' const versions = data.versions || [] const qPsv = props.psvId @@ -78,16 +94,107 @@ function load() { version.psvId = current.psvId version.versionNo = current.versionNo || 'V1' version.effectiveTime = current.effectiveTime + version.status = current.status version.priceBuildMode = current.priceBuildMode + savedPriceBuildMode.value = current.priceBuildMode details.value = [] products.value = (current.products || []).map(p => ({ ...p })) + savedProductsSnapshot.value = products.value.map(p => ({ ...p })) loaded.value = true }).finally(() => { loading.value = false }) } +function onPriceBuildModeUpdate(mode) { + if (!mode || isBuildModeLockedVersion(version.status)) return + if (mode === version.priceBuildMode) return + const leavingSaved = String(version.priceBuildMode || '') === String(savedPriceBuildMode.value || '') + if (leavingSaved) { + savedProductsSnapshot.value = (products.value || []).map(p => ({ ...p })) + } + version.priceBuildMode = mode + if (detailsRef.value && detailsRef.value.clearLocalDraft) { + detailsRef.value.clearLocalDraft() + } + details.value = [] + const backToSaved = String(mode || '') === String(savedPriceBuildMode.value || '') + if (backToSaved) { + products.value = (savedProductsSnapshot.value || []).map(p => ({ ...p })) + if (!products.value.length) { + reloadSavedProducts() + } else { + proxy.$modal.msgSuccess("已恢复原建价方式及产品列表(未保存,库数据未变)") + } + return + } + products.value = [] + proxy.$modal.msgSuccess("已切换建价方式(本地),请点「完成」保存后再拉取或导入;切回原方式可恢复列表") +} + +function reloadSavedProducts() { + const id = props.priceSetId + if (!id) return + loading.value = true + getPriceSet(id).then(res => { + const data = res.data || {} + const versions = data.versions || [] + const qPsv = props.psvId || version.psvId + const byQuery = qPsv != null ? versions.find(v => String(v.psvId) === String(qPsv)) : null + const current = byQuery || data.version || versions[0] || {} + products.value = (current.products || []).map(p => ({ ...p })) + savedProductsSnapshot.value = products.value.map(p => ({ ...p })) + proxy.$modal.msgSuccess("已恢复原建价方式及产品列表(未保存,库数据未变)") + }).finally(() => { + loading.value = false + }) +} + +async function finish() { + if (!props.priceSetId) { + close() + return + } + if (!buildModeDirty.value) { + close() + return + } + if (!version.priceBuildMode) { + proxy.$modal.msgWarning("请先选择创建价格方式") + return + } + saving.value = true + try { + const payload = { + priceSetId: props.priceSetId, + priceSetName: priceSetName.value, + setType: setType.value || 'FIXED', + version: { + psvId: version.psvId, + versionNo: version.versionNo, + priceBuildMode: version.priceBuildMode + } + } + await savePriceSetAsync(payload) + savedPriceBuildMode.value = version.priceBuildMode + savedProductsSnapshot.value = (products.value || []).map(p => ({ ...p })) + await nextTick() + if (version.priceBuildMode === 'PULL' && detailsRef.value && detailsRef.value.pullAllFromLibrary) { + await detailsRef.value.pullAllFromLibrary({ silent: true }) + proxy.$modal.msgSuccess("建价方式已保存并清空旧明细,已重新拉取产品列表") + } else { + proxy.$modal.msgSuccess("建价方式已保存并清空旧明细,可导入 Excel 建价") + } + close() + } catch (e) { + const msg = (e && e.task && e.task.error) || (e && e.message) || "保存失败" + proxy.$modal.msgError(msg) + } finally { + saving.value = false + } +} + function close() { emit('close') } 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 216dd676..8acff56c 100644 --- a/bms-front/src/views/price/set/components/create/StepDetails.vue +++ b/bms-front/src/views/price/set/components/create/StepDetails.vue @@ -17,21 +17,21 @@ v-if="showImportAction" type="primary" size="small" - :disabled="(livePersist && !priceSetId) || pulling" + :disabled="(livePersist && !priceSetId) || pulling || buildModeDirty" @click="openImport" >{{ importActionLabel }} -
+

创建价格方式