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.
This commit is contained in:
@@ -11,13 +11,14 @@
|
||||
</div>
|
||||
<div class="panel-actions">
|
||||
<el-button @click="close">关闭</el-button>
|
||||
<el-button type="primary" :disabled="!priceSetId" @click="close">完成</el-button>
|
||||
<el-button type="primary" :loading="saving" :disabled="!priceSetId" @click="finish">完成</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<step-details
|
||||
v-if="loaded"
|
||||
ref="detailsRef"
|
||||
:details="details"
|
||||
:products="products"
|
||||
:price-set-id="priceSetId"
|
||||
@@ -25,9 +26,13 @@
|
||||
@update:psv-id="v => { if (v != null && v !== '') version.psvId = v }"
|
||||
:set-type="setType"
|
||||
:price-build-mode="version.priceBuildMode"
|
||||
@update:price-build-mode="onPriceBuildModeUpdate"
|
||||
:effective-time="version.effectiveTime"
|
||||
:version-no="version.versionNo"
|
||||
:version-status="version.status"
|
||||
:ref-data="refData"
|
||||
:live-persist="true"
|
||||
:build-mode-dirty="buildModeDirty"
|
||||
header-title="编辑产品价格"
|
||||
/>
|
||||
</div>
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -17,21 +17,21 @@
|
||||
v-if="showImportAction"
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="(livePersist && !priceSetId) || pulling"
|
||||
:disabled="(livePersist && !priceSetId) || pulling || buildModeDirty"
|
||||
@click="openImport"
|
||||
>{{ importActionLabel }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="showBuildChoice" class="build-choice">
|
||||
<div class="build-choice">
|
||||
<p class="build-choice__tip">创建价格方式</p>
|
||||
<div class="build-choice__cards">
|
||||
<button
|
||||
type="button"
|
||||
class="build-card build-card--pull"
|
||||
:class="{ selected: isPullBuildMode(priceBuildMode) }"
|
||||
:disabled="choosingMode"
|
||||
:disabled="buildModeLocked || choosingMode"
|
||||
@click="chooseBuildMode('PULL')"
|
||||
>
|
||||
<div class="build-card__title">按产品价建价</div>
|
||||
@@ -41,7 +41,7 @@
|
||||
type="button"
|
||||
class="build-card build-card--import"
|
||||
:class="{ selected: isImportBuildMode(priceBuildMode) }"
|
||||
:disabled="!isFixedType(setType) || choosingMode"
|
||||
:disabled="buildModeLocked || !isFixedType(setType) || choosingMode"
|
||||
@click="chooseBuildMode('IMPORT')"
|
||||
>
|
||||
<div class="build-card__title">Excel 导入建价</div>
|
||||
@@ -152,6 +152,7 @@ import { useDetailCascade } from '../../composables/usePriceSetRefData'
|
||||
import {
|
||||
createEmptyDetailRow,
|
||||
formatWeightLabel,
|
||||
isBuildModeLockedVersion,
|
||||
isDiscountType,
|
||||
isFixedType,
|
||||
isImportBuildMode,
|
||||
@@ -174,7 +175,11 @@ const props = defineProps({
|
||||
refData: { type: Object, required: true },
|
||||
headerTitle: { type: String, default: '第 2 步 · 编辑产品价格' },
|
||||
/** true=edit live DB; false=create local draft until finish */
|
||||
livePersist: { type: Boolean, default: true }
|
||||
livePersist: { type: Boolean, default: true },
|
||||
/** 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 }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:psvId', 'update:priceBuildMode'])
|
||||
@@ -221,14 +226,11 @@ const importProgressStatus = ref(undefined)
|
||||
const progressDialogTitle = ref('正在导入价格')
|
||||
const hasBuildMode = computed(() => {
|
||||
if (isPullBuildMode(props.priceBuildMode) || isImportBuildMode(props.priceBuildMode)) return true
|
||||
// Legacy / already has products: skip choice cards
|
||||
// Legacy / already has products: treat as build mode ready
|
||||
return (props.products || []).some(p => p && p.productCode)
|
||||
})
|
||||
/** Create/newVersion only: show choice until a mode is picked */
|
||||
const showBuildChoice = computed(() => {
|
||||
if (props.livePersist) return false
|
||||
return !isPullBuildMode(props.priceBuildMode) && !isImportBuildMode(props.priceBuildMode)
|
||||
})
|
||||
/** Started (non-draft) versions: show cards but disable switching */
|
||||
const buildModeLocked = computed(() => isBuildModeLockedVersion(props.versionStatus))
|
||||
const choosingMode = ref(false)
|
||||
const isModifyImport = computed(() => {
|
||||
if (isPullBuildMode(props.priceBuildMode)) return true
|
||||
@@ -255,7 +257,6 @@ const importDesc = computed(() => {
|
||||
})
|
||||
|
||||
const typeHint = computed(() => {
|
||||
if (props.livePersist) return ''
|
||||
if (isPullBuildMode(props.priceBuildMode)) return '· 按产品价建价'
|
||||
if (isImportBuildMode(props.priceBuildMode)) return '· Excel 导入'
|
||||
if (hasBuildMode.value) return '· 当前:' + setTypeLabel(props.setType)
|
||||
@@ -367,7 +368,7 @@ const pulling = ref(false)
|
||||
function noop() {}
|
||||
|
||||
async function chooseBuildMode(mode) {
|
||||
if (!mode || choosingMode.value || props.livePersist) return
|
||||
if (!mode || choosingMode.value || buildModeLocked.value) return
|
||||
if (mode === props.priceBuildMode) return
|
||||
choosingMode.value = true
|
||||
try {
|
||||
@@ -405,6 +406,10 @@ function applyLocalDraft(products, details) {
|
||||
}
|
||||
|
||||
async function openImport() {
|
||||
if (props.buildModeDirty) {
|
||||
proxy.$modal.msgWarning("请先点「完成」保存建价方式")
|
||||
return
|
||||
}
|
||||
if (props.livePersist && !props.priceSetId) {
|
||||
proxy.$modal.msgWarning('缺少套餐ID')
|
||||
return
|
||||
|
||||
@@ -27,8 +27,10 @@
|
||||
:price-build-mode="form.version.priceBuildMode"
|
||||
@update:price-build-mode="onPriceBuildModeUpdate"
|
||||
:version-no="form.version.versionNo"
|
||||
:version-status="form.version.status"
|
||||
:ref-data="refData"
|
||||
:live-persist="livePersist"
|
||||
:build-mode-dirty="buildModeDirty"
|
||||
:header-title="detailsHeaderTitle"
|
||||
/>
|
||||
|
||||
@@ -64,6 +66,7 @@ import StepDetails from './components/create/StepDetails.vue'
|
||||
import { usePriceSetRefData } from './composables/usePriceSetRefData'
|
||||
import {
|
||||
createEmptyBasicInfo,
|
||||
isBuildModeLockedVersion,
|
||||
nextVersionNo,
|
||||
toCustomerPayload,
|
||||
toDetailPayload
|
||||
@@ -102,11 +105,16 @@ const detailsHeaderTitle = computed(() =>
|
||||
const form = reactive({
|
||||
priceSetId: undefined,
|
||||
basic: createEmptyBasicInfo(),
|
||||
version: { psvId: undefined, versionNo: 'V1', effectiveTime: undefined, expireTime: undefined, priceBuildMode: undefined },
|
||||
version: { psvId: undefined, versionNo: 'V1', effectiveTime: undefined, expireTime: undefined, status: undefined, priceBuildMode: undefined },
|
||||
details: [],
|
||||
products: [],
|
||||
customers: []
|
||||
})
|
||||
const savedPriceBuildMode = ref(undefined)
|
||||
const savedProductsSnapshot = ref([])
|
||||
const buildModeDirty = computed(() =>
|
||||
String(form.version.priceBuildMode || '') !== String(savedPriceBuildMode.value || '')
|
||||
)
|
||||
|
||||
function mapSaveProgressMessage(message) {
|
||||
const m = message || ''
|
||||
@@ -178,7 +186,33 @@ async function finishEdit() {
|
||||
goBack()
|
||||
return
|
||||
}
|
||||
proxy.$modal.msgSuccess('修改已实时落库')
|
||||
if (buildModeDirty.value) {
|
||||
if (!form.version.priceBuildMode) {
|
||||
proxy.$modal.msgWarning("请先选择创建价格方式")
|
||||
return
|
||||
}
|
||||
creating.value = true
|
||||
try {
|
||||
const payload = buildPayload()
|
||||
delete payload.pullFromLibrary
|
||||
delete payload.forcePullLibrary
|
||||
delete payload.details
|
||||
await runSaveWithProgress(payload)
|
||||
savedPriceBuildMode.value = form.version.priceBuildMode
|
||||
savedProductsSnapshot.value = (form.products || []).map(p => ({ ...p }))
|
||||
await nextTick()
|
||||
if (form.version.priceBuildMode === 'PULL' && detailsRef.value && detailsRef.value.pullAllFromLibrary) {
|
||||
await detailsRef.value.pullAllFromLibrary({ silent: true })
|
||||
}
|
||||
proxy.$modal.msgSuccess("建价方式已保存")
|
||||
} catch (e) {
|
||||
return
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
} else {
|
||||
proxy.$modal.msgSuccess("修改已实时落库")
|
||||
}
|
||||
const obj = {
|
||||
path: '/customer/setDetail',
|
||||
query: { priceSetId: form.priceSetId }
|
||||
@@ -299,42 +333,59 @@ function onPsvIdUpdate(psvId) {
|
||||
}
|
||||
|
||||
/** Step2 chose PULL / IMPORT: edit live-saves mode; create only local + optional preview pull. */
|
||||
async function onPriceBuildModeUpdate(mode) {
|
||||
function onPriceBuildModeUpdate(mode) {
|
||||
if (!mode) return
|
||||
const prev = form.version.priceBuildMode
|
||||
if (prev === mode) return
|
||||
form.version.priceBuildMode = mode
|
||||
try {
|
||||
// Create/copy: switching build mode clears the product table before reload/import.
|
||||
if (!livePersist.value && prev) {
|
||||
form.details.splice(0, form.details.length)
|
||||
form.products.splice(0, form.products.length)
|
||||
if (detailsRef.value && detailsRef.value.clearLocalDraft) {
|
||||
detailsRef.value.clearLocalDraft()
|
||||
}
|
||||
}
|
||||
if (livePersist.value) {
|
||||
const payload = buildPayload()
|
||||
delete payload.pullFromLibrary
|
||||
delete payload.forcePullLibrary
|
||||
delete payload.details
|
||||
await runSaveWithProgress(payload)
|
||||
}
|
||||
await nextTick()
|
||||
if (mode === 'PULL' && detailsRef.value && detailsRef.value.pullAllFromLibrary) {
|
||||
const hasProducts = (form.products || []).some(p => p && p.productCode)
|
||||
if (!hasProducts) {
|
||||
await detailsRef.value.pullAllFromLibrary({ silent: true })
|
||||
proxy.$modal.msgSuccess('已拉取产品列表,可继续编辑价格')
|
||||
} else if (prev !== mode) {
|
||||
proxy.$modal.msgSuccess('已切换为按产品价建价,可导入 Excel 修改价格')
|
||||
}
|
||||
} else if (mode === 'IMPORT' && prev !== mode) {
|
||||
proxy.$modal.msgSuccess('请导入 Excel 建价')
|
||||
}
|
||||
} catch (e) {
|
||||
form.version.priceBuildMode = prev
|
||||
if (isBuildModeLockedVersion(form.version.status)) return
|
||||
if (mode === form.version.priceBuildMode) return
|
||||
const leavingSaved = livePersist.value
|
||||
&& String(form.version.priceBuildMode || '') === String(savedPriceBuildMode.value || '')
|
||||
if (leavingSaved) {
|
||||
savedProductsSnapshot.value = (form.products || []).map(p => ({ ...p }))
|
||||
}
|
||||
form.version.priceBuildMode = mode
|
||||
form.details.splice(0, form.details.length)
|
||||
if (detailsRef.value && detailsRef.value.clearLocalDraft) {
|
||||
detailsRef.value.clearLocalDraft()
|
||||
}
|
||||
if (livePersist.value) {
|
||||
const backToSaved = String(mode || '') === String(savedPriceBuildMode.value || '')
|
||||
if (backToSaved) {
|
||||
form.products.splice(0, form.products.length, ...(savedProductsSnapshot.value || []).map(p => ({ ...p })))
|
||||
if (!form.products.length && form.priceSetId) {
|
||||
reloadSavedProductsForCreate()
|
||||
} else {
|
||||
proxy.$modal.msgSuccess("已恢复原建价方式及产品列表(未保存,库数据未变)")
|
||||
}
|
||||
return
|
||||
}
|
||||
form.products.splice(0, form.products.length)
|
||||
proxy.$modal.msgSuccess("已切换建价方式(本地),请点「完成」保存后再拉取或导入;切回原方式可恢复列表")
|
||||
return
|
||||
}
|
||||
form.products.splice(0, form.products.length)
|
||||
if (mode === 'PULL' && detailsRef.value && detailsRef.value.pullAllFromLibrary) {
|
||||
nextTick(() => {
|
||||
detailsRef.value.pullAllFromLibrary({ silent: true }).then(() => {
|
||||
proxy.$modal.msgSuccess("已拉取产品列表,可继续编辑价格")
|
||||
}).catch(() => {})
|
||||
})
|
||||
} else if (mode === 'IMPORT') {
|
||||
proxy.$modal.msgSuccess("请导入 Excel 建价")
|
||||
}
|
||||
}
|
||||
|
||||
function reloadSavedProductsForCreate() {
|
||||
getPriceSet(form.priceSetId).then(res => {
|
||||
const data = res.data || {}
|
||||
const versions = data.versions || []
|
||||
const qPsv = form.version.psvId
|
||||
const byQuery = qPsv != null ? versions.find(v => String(v.psvId) === String(qPsv)) : null
|
||||
const current = byQuery || data.version || versions[0] || {}
|
||||
const list = (current.products || []).map(p => ({ ...p }))
|
||||
form.products.splice(0, form.products.length, ...list)
|
||||
savedProductsSnapshot.value = list.map(p => ({ ...p }))
|
||||
proxy.$modal.msgSuccess("已恢复原建价方式及产品列表(未保存,库数据未变)")
|
||||
})
|
||||
}
|
||||
|
||||
function applySetData(data, asCopy, asNewVersion) {
|
||||
@@ -355,7 +406,9 @@ function applySetData(data, asCopy, asNewVersion) {
|
||||
form.version.versionNo = nextVersionNo(versions)
|
||||
form.version.effectiveTime = undefined
|
||||
form.version.expireTime = undefined
|
||||
form.version.status = undefined
|
||||
form.version.priceBuildMode = undefined
|
||||
savedPriceBuildMode.value = undefined
|
||||
form.details = []
|
||||
form.products = []
|
||||
} else if (asNewVersion) {
|
||||
@@ -364,7 +417,9 @@ function applySetData(data, asCopy, asNewVersion) {
|
||||
form.version.versionNo = current.versionNo || nextVersionNo(versions)
|
||||
form.version.effectiveTime = undefined
|
||||
form.version.expireTime = undefined
|
||||
form.version.status = current.status || '0'
|
||||
form.version.priceBuildMode = undefined
|
||||
savedPriceBuildMode.value = undefined
|
||||
form.details = []
|
||||
form.products = []
|
||||
} else {
|
||||
@@ -372,9 +427,12 @@ function applySetData(data, asCopy, asNewVersion) {
|
||||
form.version.versionNo = current.versionNo || nextVersionNo(versions)
|
||||
form.version.effectiveTime = current.effectiveTime
|
||||
form.version.expireTime = current.expireTime
|
||||
form.version.status = current.status
|
||||
form.version.priceBuildMode = current.priceBuildMode || undefined
|
||||
savedPriceBuildMode.value = current.priceBuildMode || undefined
|
||||
form.details = []
|
||||
form.products = (current.products || []).map(p => ({ ...p }))
|
||||
savedProductsSnapshot.value = form.products.map(p => ({ ...p }))
|
||||
}
|
||||
form.customers = (data.customers || []).map(c => ({ ...c }))
|
||||
}
|
||||
|
||||
@@ -91,6 +91,17 @@ export function isPriceEditableVersion(row) {
|
||||
return isDraftVersion(row) || isPendingVersion(row)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build-mode cards stay visible; locked for enabled/expired versions (已生效/已失效).
|
||||
* Draft / pending / create wizard remain selectable.
|
||||
*/
|
||||
export function isBuildModeLockedVersion(rowOrStatus) {
|
||||
const s = rowOrStatus && typeof rowOrStatus === 'object' ? rowOrStatus.status : rowOrStatus
|
||||
if (s == null || s === '') return false
|
||||
const status = String(s)
|
||||
return status === VER_STATUS.EFFECT || status === VER_STATUS.EXPIRED
|
||||
}
|
||||
|
||||
export function nextVersionNo(versions) {
|
||||
const seqs = (versions || []).map(v => Number(v.versionSeq) || 0)
|
||||
const max = seqs.length ? Math.max.apply(null, seqs) : 0
|
||||
|
||||
Reference in New Issue
Block a user