修改套餐价格
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* 避免产品 / 产品国家 / 附加费 被重复请求。
|
||||
*/
|
||||
import { listProduct, listProductCountries } from '@/api/price/product'
|
||||
import { listSurchargeByProduct } from '@/api/price/surcharge'
|
||||
import { listSurchargeByProduct, listSurchargeOptions } from '@/api/price/surcharge'
|
||||
import {
|
||||
buildCountryOptions,
|
||||
buildZoneOptions,
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
countryKeyOf,
|
||||
formatWeightLabel,
|
||||
matchCountryZone,
|
||||
surchargeAppliesToRow,
|
||||
toEditableDetailRow
|
||||
} from '../utils/priceSet'
|
||||
|
||||
@@ -25,20 +26,30 @@ export function usePriceSetRefData() {
|
||||
const productOptions = ref([])
|
||||
/** productCode -> bms_product_country 行 */
|
||||
const countryCache = reactive({})
|
||||
/** productCode -> 进行中的国家明细请求(去重,避免 hydrate 按行打爆接口) */
|
||||
const countryPending = {}
|
||||
/** productCode -> 有效附加费关联 */
|
||||
const surchargeCache = reactive({})
|
||||
/** productCode -> 进行中的请求 */
|
||||
const surchargePending = {}
|
||||
/** 价格基础 → 附加费配置 主表全量(勾选弹窗数据源,页面内共享一份) */
|
||||
const allSurchargeList = ref(null)
|
||||
let allSurchargePending = null
|
||||
|
||||
const productMap = computed(() => new Map(productOptions.value.map(p => [p.value, p])))
|
||||
|
||||
/** 加载可选产品(已生效) */
|
||||
/** 加载可选产品(已生效),保留列表展示字段 */
|
||||
function loadProducts() {
|
||||
return listProduct({ status: '1', pageNum: 1, pageSize: 1000 }).then(res => {
|
||||
productOptions.value = (res.rows || []).map(p => ({
|
||||
value: p.productCode,
|
||||
label: `${p.productCode} ${p.productName || ''}`.trim(),
|
||||
name: p.productName
|
||||
name: p.productName,
|
||||
serviceLevel: p.serviceLevel,
|
||||
cargoAttr: p.cargoAttr,
|
||||
destRegion: p.destRegion,
|
||||
taxMode: p.taxMode,
|
||||
countryCount: p.countryCount
|
||||
}))
|
||||
return productOptions.value
|
||||
})
|
||||
@@ -50,15 +61,72 @@ export function usePriceSetRefData() {
|
||||
return hit ? hit.name : productCode
|
||||
}
|
||||
|
||||
/** 确保产品的国家×重量段配置已加载(过滤已失效配置) */
|
||||
function ensureCountries(productCode) {
|
||||
/** product meta for step-3 product list */
|
||||
function productMetaOf(productCode) {
|
||||
return productMap.value.get(productCode) || null
|
||||
}
|
||||
|
||||
/** YYYY-MM-DD from datetime / date */
|
||||
function dateOnly(v) {
|
||||
if (!v) return null
|
||||
return String(v).slice(0, 10)
|
||||
}
|
||||
|
||||
/**
|
||||
* Country row active at asOf (version effective time).
|
||||
* Window: effective_date <= asOf AND (expire_date is null OR expire_date > asOf)
|
||||
*/
|
||||
function isCountryActiveAt(item, asOf) {
|
||||
const t = dateOnly(asOf)
|
||||
if (!t) return true
|
||||
const eff = dateOnly(item.effectiveDate)
|
||||
const exp = dateOnly(item.expireDate)
|
||||
if (eff && eff > t) return false
|
||||
if (exp && exp <= t) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/** currently open country rows (not expired) */
|
||||
function isCountryOpen(item) {
|
||||
if (String(item.status) === '2') return false
|
||||
const exp = dateOnly(item.expireDate)
|
||||
if (!exp) return true
|
||||
const today = dateOnly(new Date().toISOString())
|
||||
return exp > today
|
||||
}
|
||||
|
||||
/**
|
||||
* Load product country×weight rows.
|
||||
* Cache keeps full list; pass asOf (version effective_time) to filter the active window.
|
||||
* If asOf match is empty, fall back to currently open rows so step-3 can still show full product prices.
|
||||
* In-flight requests are deduped per productCode (hydrateRow is per-detail-row).
|
||||
*/
|
||||
function ensureCountries(productCode, asOf) {
|
||||
if (!productCode) return Promise.resolve([])
|
||||
if (countryCache[productCode]) return Promise.resolve(countryCache[productCode])
|
||||
return listProductCountries(productCode).then(res => {
|
||||
const rows = (res.data || []).filter(item => !item.expireDate)
|
||||
const filterRows = (rows) => {
|
||||
const all = rows || []
|
||||
if (!asOf) return all.filter(isCountryOpen)
|
||||
const hit = all.filter(item => isCountryActiveAt(item, asOf))
|
||||
if (hit.length) return hit
|
||||
return all.filter(isCountryOpen)
|
||||
}
|
||||
if (countryCache[productCode]) {
|
||||
return Promise.resolve(filterRows(countryCache[productCode]))
|
||||
}
|
||||
if (countryPending[productCode]) {
|
||||
return countryPending[productCode].then(filterRows)
|
||||
}
|
||||
countryPending[productCode] = listProductCountries(productCode).then(res => {
|
||||
const rows = res.data || []
|
||||
countryCache[productCode] = rows
|
||||
return rows
|
||||
}).catch(() => {
|
||||
// do not cache failure; allow later retry without stampeding while in-flight
|
||||
return []
|
||||
}).finally(() => {
|
||||
delete countryPending[productCode]
|
||||
})
|
||||
return countryPending[productCode].then(filterRows)
|
||||
}
|
||||
|
||||
/** 确保产品的有效附加费已加载(剔除 OF 燃油,结果缓存 + 请求去重) */
|
||||
@@ -71,7 +139,7 @@ export function usePriceSetRefData() {
|
||||
surchargeCache[productCode] = rows
|
||||
return rows
|
||||
}).catch(() => {
|
||||
surchargeCache[productCode] = []
|
||||
// 失败不写缓存,后续可重试(避免一次抖动就让该产品永久显示无附加费)
|
||||
return []
|
||||
}).finally(() => {
|
||||
delete surchargePending[productCode]
|
||||
@@ -79,18 +147,41 @@ export function usePriceSetRefData() {
|
||||
return surchargePending[productCode]
|
||||
}
|
||||
|
||||
/** 产品已关联的附加费全量(不按国家/重量段过滤),供套餐勾选使用 */
|
||||
function allSurchargesOf(productCode) {
|
||||
if (!productCode) return []
|
||||
return surchargeCache[productCode] || []
|
||||
}
|
||||
|
||||
/** 确保附加费配置主表已加载(剔除 OF 燃油,页面内共享一份缓存) */
|
||||
function ensureAllSurcharges() {
|
||||
if (allSurchargeList.value) return Promise.resolve(allSurchargeList.value)
|
||||
if (!allSurchargePending) {
|
||||
allSurchargePending = listSurchargeOptions().then(res => {
|
||||
// /options 返回 AjaxResult(data),兼容分页接口的 rows 写法
|
||||
const raw = res.data || res.rows || []
|
||||
const rows = raw.filter(item => String(item.ssCode || '').toLowerCase() !== 'of')
|
||||
allSurchargeList.value = rows
|
||||
return rows
|
||||
}).catch(() => {
|
||||
// 失败不缓存,允许下次重试;调用方拿空数组也不至于报错中断
|
||||
return []
|
||||
}).finally(() => {
|
||||
allSurchargePending = null
|
||||
})
|
||||
}
|
||||
return allSurchargePending
|
||||
}
|
||||
|
||||
/** 附加费配置主表全量(需先 ensureAllSurcharges) */
|
||||
function allSurchargeConfigs() {
|
||||
return allSurchargeList.value || []
|
||||
}
|
||||
|
||||
/** 明细行命中的附加费(按产品 + 国家 + 重量段过滤) */
|
||||
function surchargesOf(row) {
|
||||
if (!row || !row.productCode) return []
|
||||
const all = surchargeCache[row.productCode] || []
|
||||
return all.filter(s => {
|
||||
if (s.countryCode && row.countryCode && String(s.countryCode).toUpperCase() !== String(row.countryCode).toUpperCase()) {
|
||||
return false
|
||||
}
|
||||
if (s.detailId == null) return true
|
||||
if (row.productCountryId != null && Number(s.detailId) === Number(row.productCountryId)) return true
|
||||
return false
|
||||
})
|
||||
return (surchargeCache[row.productCode] || []).filter(s => surchargeAppliesToRow(s, row))
|
||||
}
|
||||
|
||||
/** 明细行可选国家 */
|
||||
@@ -115,8 +206,12 @@ export function usePriceSetRefData() {
|
||||
productOptions,
|
||||
loadProducts,
|
||||
productNameOf,
|
||||
productMetaOf,
|
||||
ensureCountries,
|
||||
ensureSurcharges,
|
||||
allSurchargesOf,
|
||||
ensureAllSurcharges,
|
||||
allSurchargeConfigs,
|
||||
surchargesOf,
|
||||
countryOptionsOf,
|
||||
zoneOptionsOf,
|
||||
@@ -140,9 +235,13 @@ export function useDetailCascade(refData) {
|
||||
row.zoneCode = undefined
|
||||
row.zoneName = undefined
|
||||
row.weightLabel = undefined
|
||||
row.weightMin = undefined
|
||||
row.weightMax = undefined
|
||||
row.productCountryId = undefined
|
||||
row.fvValue = undefined
|
||||
row.fvRegistrationFee = undefined
|
||||
row.effectiveDate = undefined
|
||||
row.expireDate = undefined
|
||||
}
|
||||
|
||||
/** 选择产品:回填名称、清空级联、预取附加费与国家配置 */
|
||||
@@ -163,9 +262,13 @@ export function useDetailCascade(refData) {
|
||||
row.zoneCode = undefined
|
||||
row.zoneName = undefined
|
||||
row.weightLabel = undefined
|
||||
row.weightMin = undefined
|
||||
row.weightMax = undefined
|
||||
row.productCountryId = undefined
|
||||
row.fvValue = undefined
|
||||
row.fvRegistrationFee = undefined
|
||||
row.effectiveDate = undefined
|
||||
row.expireDate = undefined
|
||||
}
|
||||
|
||||
function onZoneChange(row) {
|
||||
@@ -174,9 +277,13 @@ export function useDetailCascade(refData) {
|
||||
row.zoneCode = opt ? opt.zoneCode : undefined
|
||||
row.zoneName = opt ? opt.zoneName : undefined
|
||||
row.weightLabel = undefined
|
||||
row.weightMin = undefined
|
||||
row.weightMax = undefined
|
||||
row.productCountryId = undefined
|
||||
row.fvValue = undefined
|
||||
row.fvRegistrationFee = undefined
|
||||
row.effectiveDate = undefined
|
||||
row.expireDate = undefined
|
||||
}
|
||||
|
||||
/** 选择重量段:绑定产品国家明细,并带出默认运费 / 挂号费 */
|
||||
@@ -184,6 +291,10 @@ export function useDetailCascade(refData) {
|
||||
const opt = refData.weightOptionsOf(row).find(w => w.value === row.weightLabel)
|
||||
if (!opt) {
|
||||
row.productCountryId = undefined
|
||||
row.weightMin = undefined
|
||||
row.weightMax = undefined
|
||||
row.effectiveDate = undefined
|
||||
row.expireDate = undefined
|
||||
return
|
||||
}
|
||||
row.productCountryId = opt.detailId
|
||||
@@ -192,14 +303,32 @@ export function useDetailCascade(refData) {
|
||||
if (opt.zoneName) row.zoneName = opt.zoneName
|
||||
if (opt.freightPrice != null) row.fvValue = Number(opt.freightPrice)
|
||||
if (opt.registrationFee != null) row.fvRegistrationFee = Number(opt.registrationFee)
|
||||
row.weightMin = opt.weightMin
|
||||
row.weightMax = opt.weightMax
|
||||
row.effectiveDate = opt.effectiveDate
|
||||
row.expireDate = opt.expireDate
|
||||
}
|
||||
|
||||
/** hydrate detail row; normalize weight label to min<W\u2264max */
|
||||
function hydrateRow(d, setType) {
|
||||
const row = toEditableDetailRow(d)
|
||||
if (!row.productCode) return row
|
||||
refData.ensureSurcharges(row.productCode)
|
||||
// Do NOT prefetch /surcharge/byProduct here: applySetData hydrates every detail row
|
||||
// on step-1 load; use version snapshot row.surcharges instead. Fetch on demand in picker.
|
||||
if (setType !== 'FIXED') return row
|
||||
// Bulk load from DB already has productCountryId + weight bounds: normalize locally,
|
||||
// do not fire /countries per detail row (was stampeding thousands of identical requests).
|
||||
if (row.productCountryId != null && (row.weightMin != null || row.weightMax != null || row.weightLabel)) {
|
||||
if (row.weightMin != null || row.weightMax != null) {
|
||||
row.weightLabel = formatWeightLabel(row.weightMin, row.weightMax)
|
||||
}
|
||||
row._countryKey = countryKeyOf(row)
|
||||
// Snapshot from DB: treat current package price as baseline so drawer cells stay editable
|
||||
// and yellow "changed" highlight still works after first edit.
|
||||
if (row._baseFreight == null && row.fvValue != null) row._baseFreight = Number(row.fvValue)
|
||||
if (row._baseReg == null && row.fvRegistrationFee != null) row._baseReg = Number(row.fvRegistrationFee)
|
||||
return row
|
||||
}
|
||||
refData.ensureCountries(row.productCode).then(rows => {
|
||||
if (!row.weightLabel) return
|
||||
const hit = rows.find(item => {
|
||||
@@ -217,6 +346,12 @@ export function useDetailCascade(refData) {
|
||||
if (hit.zoneId != null) row.zoneId = hit.zoneId
|
||||
if (hit.zoneCode) row.zoneCode = hit.zoneCode
|
||||
if (hit.zoneName) row.zoneName = hit.zoneName
|
||||
row.weightMin = hit.weightMin
|
||||
row.weightMax = hit.weightMax
|
||||
row.effectiveDate = hit.effectiveDate
|
||||
row.expireDate = hit.expireDate
|
||||
if (row._baseFreight == null && hit.freightPrice != null) row._baseFreight = Number(hit.freightPrice)
|
||||
if (row._baseReg == null && hit.registrationFee != null) row._baseReg = Number(hit.registrationFee)
|
||||
})
|
||||
return row
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user