feat(pl): hard-block over-limit contract qty on arrange and remind after success

Validate arranged+this plan qty against fixed contract product quantities on
transport arrange; return post-success remaining/framework cumulative reminders.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-30 16:47:21 +08:00
1 parent d326996976
commit 3011cb9dae
10 files changed
+1424 -8

No files matched your search

+7 -1
View File
@@ -149,7 +149,13 @@ bxAxios.interceptors.response.use(
await userStore.logOut()
} else {
const detail = err.response?.data?.detail || err.response?.data?.msg
ElMessage.error(detail || err.message || '网络异常')
const message =
typeof detail === 'string'
? detail
: detail && typeof detail === 'object'
? detail.message || detail.msg || JSON.stringify(detail)
: detail
ElMessage.error(message || err.message || '网络异常')
}
return Promise.reject(err)
}
@@ -0,0 +1,108 @@
import { ElMessageBox } from 'element-plus'
import type { ContractQtyReminder, ContractQtyReminderItem } from '@/api/pl'
export const CONTRACT_QTY_VIOLATION_CODE = 'CONTRACT_QTY_OVERFLOW'
function formatQty(n: number | undefined | null): string {
if (n == null || Number.isNaN(Number(n))) return '—'
return Number(n).toFixed(3)
}
function stopKindLabel(kind?: string): string {
if (kind === 'delivery') return '送货'
if (kind === 'load') return '装货'
return kind || ''
}
export function extractContractQtyViolationDetail(error: unknown): {
message: string
items: Array<Record<string, unknown>>
} | null {
const err = error as {
response?: { status?: number; data?: { detail?: unknown } }
}
const detail = err?.response?.data?.detail
if (!detail || typeof detail !== 'object') return null
const d = detail as { code?: string; message?: string; items?: unknown }
if (d.code !== CONTRACT_QTY_VIOLATION_CODE) return null
const items = Array.isArray(d.items) ? (d.items as Array<Record<string, unknown>>) : []
return {
message: d.message || '合同产品安排数量超过合同数量',
items
}
}
export function formatContractQtyViolationMessage(
items: Array<Record<string, unknown>>
): string {
if (!items.length) return '合同产品安排数量超过合同数量,请调整后再试。'
const lines = items.map((it) => {
const name = String(it.productName || it.productId || '产品')
const contractNo = String(it.contractNo || it.contractId || '')
const side = stopKindLabel(String(it.stopKind || ''))
const contractQty = formatQty(Number(it.contractQty))
const effective = formatQty(Number(it.effectiveTotal ?? it.arrangedTotalQty))
const overflow = formatQty(Number(it.overflowQty))
const meta = [contractNo, side].filter(Boolean).join(' · ')
return `「${name}」安排后合计 ${effective},超过合同数量 ${contractQty}(超 ${overflow})${meta ? `;${meta}` : ''}`
})
return `以下产品「已安排合计 + 本次安排」超过合同数量,请调整后再试:\n\n${lines.join('\n')}`
}
export function formatContractQtyReminderMessage(reminder: ContractQtyReminder): string {
const items = reminder?.items || []
if (!items.length) return ''
const remaining = items.filter((i) => i.mode === 'remaining')
const framework = items.filter((i) => i.mode === 'framework_cumulative')
const blocks: string[] = []
if (remaining.length) {
const lines = remaining.map((it: ContractQtyReminderItem) => {
const name = it.productName || it.productId
const meta = [it.contractNo, stopKindLabel(it.stopKind)].filter(Boolean).join(' · ')
return `「${name}」已安排合计(含本次)${formatQty(it.arrangedTotalQty)},剩余可安排 ${formatQty(it.remainingQty)}${meta ? `;${meta}` : ''}`
})
blocks.push(`合同数量校验通过。各产品剩余可安排量:\n${lines.join('\n')}`)
}
if (framework.length) {
const lines = framework.map((it: ContractQtyReminderItem) => {
const name = it.productName || it.productId
const meta = [it.contractNo, stopKindLabel(it.stopKind)].filter(Boolean).join(' · ')
return `「${name}」累计已安排 ${formatQty(it.arrangedTotalQty)}(本次 ${formatQty(it.thisPlanQty)})${meta ? `;${meta}` : ''}`
})
blocks.push(
`关联框架合同无固定数量上限。本次安排后累计已安排:\n${lines.join('\n')}`
)
}
return blocks.join('\n\n')
}
/** @returns true 表示已按合同超量处理(调用方勿再抛出/重复提示) */
export async function showContractQtyViolationIfMatch(error: unknown): Promise<boolean> {
const parsed = extractContractQtyViolationDetail(error)
if (!parsed) return false
await ElMessageBox.alert(formatContractQtyViolationMessage(parsed.items), '无法安排运输', {
type: 'error',
confirmButtonText: '知道了'
})
return true
}
export async function showContractQtyReminderIfAny(
reminder?: ContractQtyReminder | null
): Promise<void> {
const items = reminder?.items || []
if (!items.length) return
const message = formatContractQtyReminderMessage(reminder!)
if (!message) return
const hasFramework = items.some((i) => i.mode === 'framework_cumulative')
const title = hasFramework && !items.some((i) => i.mode === 'remaining')
? '安排成功 · 框架合同累计'
: '安排成功 · 合同剩余数量'
await ElMessageBox.alert(message, title, {
type: 'success',
confirmButtonText: '知道了'
})
}