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 6ee27cf3..9298e2f2 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 @@ -3415,7 +3415,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } PrcPriceSetVersion effect = null; PrcPriceSetVersion pending = null; - PrcPriceSetVersion best = null; + List ranked = new ArrayList(); for (PrcPriceSetVersion v : list) { if (v == null || v.getPsvId() == null) @@ -3434,15 +3434,58 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { pending = v; } - if (best == null || (v.getVersionSeq() != null && best.getVersionSeq() != null - && v.getVersionSeq().intValue() > best.getVersionSeq().intValue()) - || (best.getVersionSeq() == null && v.getVersionSeq() != null)) + } + if (effect != null) + { + ranked.add(effect); + } + if (pending != null && (effect == null || !pending.getPsvId().equals(effect.getPsvId()))) + { + ranked.add(pending); + } + list.stream() + .filter(v -> v != null && v.getPsvId() != null) + .filter(v -> excludePsvId == null || !excludePsvId.equals(v.getPsvId())) + .sorted((a, b) -> { + int sa = a.getVersionSeq() == null ? 0 : a.getVersionSeq().intValue(); + int sb = b.getVersionSeq() == null ? 0 : b.getVersionSeq().intValue(); + return Integer.compare(sb, sa); + }) + .forEach(v -> { + boolean exists = false; + for (PrcPriceSetVersion r : ranked) + { + if (r.getPsvId().equals(v.getPsvId())) + { + exists = true; + break; + } + } + if (!exists) + { + ranked.add(v); + } + }); + for (PrcPriceSetVersion cand : ranked) + { + if (versionHasSetProducts(cand.getPsvId())) { - best = v; + return cand.getPsvId(); } } - PrcPriceSetVersion pick = effect != null ? effect : (pending != null ? pending : best); - return pick == null ? null : pick.getPsvId(); + return ranked.isEmpty() ? null : ranked.get(0).getPsvId(); + } + + private boolean versionHasSetProducts(Long psvId) + { + if (psvId == null) + { + return false; + } + PrcPriceSetProduct pq = new PrcPriceSetProduct(); + pq.setPsvId(psvId); + List products = setProductMapper.selectPrcPriceSetProductList(pq); + return products != null && !products.isEmpty(); } /** diff --git a/bms-front/src/views/price/orderBill/index.vue b/bms-front/src/views/price/orderBill/index.vue index 518298e0..9638340d 100644 --- a/bms-front/src/views/price/orderBill/index.vue +++ b/bms-front/src/views/price/orderBill/index.vue @@ -79,6 +79,32 @@ remote-search /> + @@ -158,7 +184,8 @@ const tableHeight = ref(480) let tableResizeObserver = null const defaultParams = { - orderStatuses: [3, 4, 5, 6, 7, 8, 11] + orderStatuses: [3, 4, 5, 6, 7, 8, 11], + billingFailStatuses: [] } const columns = [ @@ -227,6 +254,29 @@ const billingFailStatusOptions = [ { label: '未知失败', value: 22 } ] +const billingFailAllValues = computed(() => billingFailStatusOptions.map(o => o.value)) + +function selectedBillingFailStatuses(model) { + return Array.isArray(model?.billingFailStatuses) ? model.billingFailStatuses : [] +} + +function isAllBillingFailSelected(model) { + const selected = selectedBillingFailStatuses(model) + const all = billingFailAllValues.value + return all.length > 0 && selected.length === all.length +} + +function isBillingFailIndeterminate(model) { + const selected = selectedBillingFailStatuses(model) + const all = billingFailAllValues.value + return selected.length > 0 && selected.length < all.length +} + +function toggleAllBillingFail(model, checked) { + if (!model) return + model.billingFailStatuses = checked ? [...billingFailAllValues.value] : [] +} + const searchColumns = [ { label: '单号', prop: 'orderIdentifiers', placeholder: '订单号/运单号/转单号' }, { label: '客户', prop: 'customerCodes', placeholder: '客户名称/客户代码/对外代码' }, @@ -268,7 +318,14 @@ const searchColumns = [ prop: 'billingFailStatuses', component: 'select', options: billingFailStatusOptions, - placeholder: '请选择' + placeholder: '请选择', + props: { + multiple: true, + filterable: true, + clearable: true, + collapseTags: true, + collapseTagsTooltip: true + } } ] 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 c5faf801..2be973d5 100644 --- a/bms-front/src/views/price/set/components/create/StepDetails.vue +++ b/bms-front/src/views/price/set/components/create/StepDetails.vue @@ -391,7 +391,9 @@ async function loadLatestPriorVersionDraft() { const res = await getPriceSet(props.priceSetId) const data = res.data || {} const versions = data.versions || [] - const prior = pickLatestPriorVersion(versions, props.psvId || resolvedPsvId.value) + // Only exclude the draft being edited. Do NOT use resolvedPsvId — it may + // point at EFFECT after resolvePsvId() and wrongly skip the real prior. + const prior = pickLatestPriorVersion(versions, props.psvId) if (!prior || prior.psvId == null) return false const products = (prior.products || []) .filter(p => p && p.productCode) diff --git a/bms-front/src/views/price/set/create.vue b/bms-front/src/views/price/set/create.vue index 33c66b3e..a8810aa0 100644 --- a/bms-front/src/views/price/set/create.vue +++ b/bms-front/src/views/price/set/create.vue @@ -377,19 +377,31 @@ function onPriceBuildModeUpdate(mode) { }).catch(() => {}) }) } else if (mode === 'IMPORT') { - nextTick(() => { + nextTick(async () => { + // newVersion/copy: wait until loadExisting fills priceSetId + const qid = route.query.priceSetId + if (!form.priceSetId && qid) { + for (let i = 0; i < 50 && !form.priceSetId; i++) { + await new Promise(r => setTimeout(r, 100)) + } + } const loader = detailsRef.value && detailsRef.value.loadLatestPriorVersionDraft if (!loader) { proxy.$modal.msgSuccess("请导入 Excel 建价") return } - loader().then(ok => { + if (!form.priceSetId) { + proxy.$modal.msgSuccess("新建套餐无历史版本可带出,请直接导入 Excel 建价") + return + } + try { + const ok = await loader() proxy.$modal.msgSuccess(ok ? "已带出上一版全部产品,导入 Excel 按产品+国家覆盖或新增" - : "暂无上一版产品,请导入 Excel 建价") - }).catch(() => { + : "未找到含产品的上一版,请导入 Excel 建价") + } catch (e) { proxy.$modal.msgSuccess("请导入 Excel 建价") - }) + } }) } } diff --git a/bms-front/src/views/price/set/utils/priceSet.js b/bms-front/src/views/price/set/utils/priceSet.js index 878489ba..26274e72 100644 --- a/bms-front/src/views/price/set/utils/priceSet.js +++ b/bms-front/src/views/price/set/utils/priceSet.js @@ -108,9 +108,14 @@ export function nextVersionNo(versions) { return 'V' + (max + 1) } +function versionHasProducts(v) { + return !!(v && (v.products || []).some(p => p && p.productCode)) +} + /** * Latest prior version for Excel build-price preload (exclude current draft). - * Prefer EFFECT, then PENDING, then highest versionSeq. + * Prefer EFFECT / PENDING / highest versionSeq that actually has products, + * so an empty EFFECT does not hide an older version that still has data. */ export function pickLatestPriorVersion(versions, excludePsvId) { const list = (versions || []).filter(v => { @@ -119,11 +124,17 @@ export function pickLatestPriorVersion(versions, excludePsvId) { return true }) if (!list.length) return null + const ranked = [] const effect = list.find(v => String(v.status) === VER_STATUS.EFFECT) - if (effect) return effect + if (effect) ranked.push(effect) const pending = list.find(v => String(v.status) === VER_STATUS.PENDING) - if (pending) return pending - return list.slice().sort((a, b) => (Number(b.versionSeq) || 0) - (Number(a.versionSeq) || 0))[0] + if (pending && (!effect || String(pending.psvId) !== String(effect.psvId))) ranked.push(pending) + const bySeq = list.slice().sort((a, b) => (Number(b.versionSeq) || 0) - (Number(a.versionSeq) || 0)) + bySeq.forEach(v => { + if (!ranked.some(x => String(x.psvId) === String(v.psvId))) ranked.push(v) + }) + const withProducts = ranked.find(versionHasProducts) + return withProducts || ranked[0] || null } /** Package status label */