Files
shanxia2.0/web-admin/src/composables/usePlToxicProductCertCheck.ts
T
2026-07-12 12:04:43 +08:00

203 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { fetchProductDetail } from '@/api/crm/product'
import type { PlanProduct } from '@/api/pl'
export type HazardFlagProduct = {
productId?: string
productName?: string
isToxic?: number
isExplosive?: number
is_toxic?: number
is_explosive?: number
}
export type HazardCertSlotKey = 'purchaseCertUrl' | 'transportCertUrl' | 'legalUseCertUrl'
export interface HazardCertSlot {
key: HazardCertSlotKey
label: string
}
const HAZARD_CERT_SLOT_LABELS: Record<HazardCertSlotKey, string> = {
purchaseCertUrl: '购买证',
transportCertUrl: '运输证',
legalUseCertUrl: '合法证明'
}
function isTruthyHazardFlag(value: unknown) {
return value === 1 || value === true || value === '1'
}
export function resolveProductHazardFlags(product: HazardFlagProduct) {
const isToxic =
isTruthyHazardFlag(product.isToxic) || isTruthyHazardFlag(product.is_toxic)
const isExplosive =
isTruthyHazardFlag(product.isExplosive) || isTruthyHazardFlag(product.is_explosive)
return { isToxic, isExplosive }
}
export function isHazardProduct(product: Pick<PlanProduct, 'isToxic' | 'isExplosive'>) {
const flags = resolveProductHazardFlags(product)
return flags.isToxic || flags.isExplosive
}
export function getHazardProductLabel(product: Pick<PlanProduct, 'isToxic' | 'isExplosive'>) {
const flags = resolveProductHazardFlags(product)
if (flags.isExplosive) return '易制爆'
if (flags.isToxic) return '易制毒'
return ''
}
/** 按产品危化属性返回所需证书槽位(单一规则源) */
export function getRequiredCertSlots(product: HazardFlagProduct): HazardCertSlot[] {
const { isToxic, isExplosive } = resolveProductHazardFlags(product)
const slots: HazardCertSlot[] = []
if (isToxic) {
slots.push({ key: 'purchaseCertUrl', label: HAZARD_CERT_SLOT_LABELS.purchaseCertUrl })
slots.push({ key: 'transportCertUrl', label: HAZARD_CERT_SLOT_LABELS.transportCertUrl })
}
if (isExplosive) {
slots.push({ key: 'legalUseCertUrl', label: HAZARD_CERT_SLOT_LABELS.legalUseCertUrl })
}
return slots
}
export function getProductCertUrl(product: PlanProduct, key: HazardCertSlotKey): string {
const value = product[key]
return typeof value === 'string' ? value.trim() : ''
}
export function isProductCertSlotFilled(product: PlanProduct, key: HazardCertSlotKey): boolean {
return Boolean(getProductCertUrl(product, key))
}
export function isProductCertComplete(product: PlanProduct): boolean {
const slots = getRequiredCertSlots(product)
if (!slots.length) return true
return slots.every(slot => isProductCertSlotFilled(product, slot.key))
}
/** 产品名称后追加易制毒/易制爆标识:毒、爆,或「毒 爆」 */
export function formatPlProductNameWithHazardMarks(product: HazardFlagProduct) {
const name = product.productName || ''
const { isToxic, isExplosive } = resolveProductHazardFlags(product)
const marks: string[] = []
if (isToxic) marks.push('毒')
if (isExplosive) marks.push('爆')
if (!marks.length) return name
return `${name}${marks.join(' ')}`
}
/** 从 CRM 产品主数据读取易制毒/易制爆标识(覆盖计划产品快照字段) */
export async function enrichPlanProductsHazardFromCrm<T extends HazardFlagProduct & { productId: string }>(
products: T[]
): Promise<T[]> {
if (!products.length) return products
const lookupIds = [
...new Set(products.map(product => product.productId).filter(Boolean))
]
if (!lookupIds.length) return products
const hazardMap = new Map<string, { isToxic: number; isExplosive: number; taxRate?: number }>()
await Promise.all(
lookupIds.map(async productId => {
try {
const res = await fetchProductDetail({ id: productId })
const data = (res as { data?: Record<string, unknown> }).data ?? res
const row = data as Record<string, unknown>
const rawTaxRate = row.taxRate ?? row.tax_rate
hazardMap.set(productId, {
isToxic: Number(row.isToxic ?? row.is_toxic ?? 0),
isExplosive: Number(row.isExplosive ?? row.is_explosive ?? 0),
taxRate: rawTaxRate != null && rawTaxRate !== '' ? Number(rawTaxRate) : undefined
})
} catch {
// 单条产品查询失败时跳过
}
})
)
return products.map(product => {
const fromCrm = hazardMap.get(product.productId)
if (!fromCrm) return product
return {
...product,
isToxic: fromCrm.isToxic,
isExplosive: fromCrm.isExplosive,
taxRate: product.taxRate ?? fromCrm.taxRate
}
})
}
export function findMissingHazardCerts(products: PlanProduct[]) {
return products.filter(p => isHazardProduct(p) && !isProductCertComplete(p))
}
export function planHasHazardProducts(products: PlanProduct[] | undefined) {
return (products ?? []).some(p => isHazardProduct(p))
}
export function isHazardCertReady(plan: { hazardCertReady?: number } | null | undefined) {
return Number(plan?.hazardCertReady ?? 0) === 1
}
/** 含危化品且尚未确认证书齐备(人工确认或全部所需证书已上传)时,阻断安排运输 */
export function shouldBlockTransportForHazardCert(
plan: { hazardCertReady?: number; products?: PlanProduct[] } | null | undefined,
products?: PlanProduct[]
) {
const prods = products ?? plan?.products
if (!planHasHazardProducts(prods)) return false
if (isHazardCertReady(plan)) return false
if (!findMissingHazardCerts(prods ?? []).length) return false
return true
}
export function buildMissingHazardCertReadyMessage(
plan: { hazardCertReady?: number; products?: PlanProduct[] } | null | undefined,
products?: PlanProduct[]
) {
if (!shouldBlockTransportForHazardCert(plan, products)) return ''
const prods = products ?? plan?.products ?? []
const missingCertsMsg = buildMissingHazardCertMessage(prods)
if (missingCertsMsg) return missingCertsMsg
return '该计划危化品证书尚未确认齐备,请点击「证书齐备」或补全所需证书'
}
function formatMissingCertLabels(product: PlanProduct): string {
const missing = getRequiredCertSlots(product)
.filter(slot => !isProductCertSlotFilled(product, slot.key))
.map(slot => slot.label)
return missing.join('、')
}
export function buildMissingHazardCertMessage(products: PlanProduct[]) {
const missing = findMissingHazardCerts(products)
if (!missing.length) return ''
const details = missing
.map(p => `「${p.productName}」须补充:${formatMissingCertLabels(p)}`)
.join(';')
return `以下产品证书尚未齐全:${details}`
}
export const HAZARD_CERT_REQUIREMENT_LINES = [
'易制毒:购买证、运输证',
'易制爆:合法证明'
] as const
export const HAZARD_CERT_READY_CONFIRM_HINT =
'计划内产品仅分为易制毒、易制爆或普通产品,同一产品不会兼具两种危化属性。' +
HAZARD_CERT_REQUIREMENT_LINES.join(';') +
'。'
/** 证书齐备确认弹窗正文(换行列表展示) */
export function buildHazardCertReadyConfirmMessage(): string {
return [
'确认该计划危化品证书已齐备?',
'',
...HAZARD_CERT_REQUIREMENT_LINES,
'',
'确认后可安排运输;之后仍可在本页补充上传附件。'
].join('<br>')
}