This commit is contained in:
zlwu committed 2026-08-21 09:35:11 +08:00
1 parent b9ff9c9d67
commit 44749ae716
87 files changed
+35089 -21918

No files matched your search

@@ -0,0 +1,167 @@
export const CRM_INVOICE_PASTE_SAMPLE_ANQING = `开票资料:
公司名称:安庆市山峡化工有限公司
纳税人识别号:9134080006084212XH
开户行:中国建设银行安庆分行城中支行
帐号:34050168860800000121
银行行号:105368000056
地址:安徽省安庆市高新区霞虹路5号
电话/传真:0556-5348918
邮编:246000
发票邮寄地址:
姓名:汪璇
电话:18895395317
地址:安徽省安庆市高新区霞虹路5号`
export const CRM_INVOICE_PASTE_SAMPLE_JIANGFENG = `单位名:安徽江峰生物科技有限公司
税 号:91340200MA8PYJNB8X
地址及电话:安徽省芜湖市皖江江北新兴产业集中区沈巷片区起步区皖跃路与和睦路交叉口西南角福州路4号
银行开户行:中国建设银行芜湖经开区支行
账 号:3405 0167 8808 0000 2481
银行行号:1053 6205 0066
电话:0553-5201019`
export interface CrmInvoicePasteResult {
companyName?: string
taxNo?: string
companyAddress?: string
legalRepresentative?: string
agent?: string
phoneFax?: string
bankName?: string
bankAccount?: string
remarkParts: string[]
warnings: string[]
filledCount: number
}
function normalizeSpaces(s: string): string {
return (s || '').replace(/\s+/g, ' ').trim()
}
function splitMailBlock(text: string): { main: string; mail: string } {
const idx = text.search(/发票邮寄地址\s*[::]?/)
if (idx < 0) return { main: text, mail: '' }
return {
main: text.slice(0, idx),
mail: text.slice(idx).replace(/^发票邮寄地址\s*[::]?/, '')
}
}
function pickLine(block: string, labelRe: RegExp): string {
const lines = block.split(/\r?\n/)
for (const raw of lines) {
const line = raw.trim()
if (!line) continue
const m = line.match(labelRe)
if (m && m[1] != null) return normalizeSpaces(m[1])
}
return ''
}
const PHONE_TAIL =
/((?:0\d{2,3}-?\d{7,8})|(?:1\d{10}))\s*$/
export function parseCrmInvoicePaste(raw: string): CrmInvoicePasteResult {
const warnings: string[] = []
const text = (raw || '').replace(/\r\n/g, '\n').trim()
if (!text) {
return { remarkParts: [], warnings: ['请先粘贴开票资料文本'], filledCount: 0 }
}
const { main, mail } = splitMailBlock(text)
const remarkParts: string[] = []
const companyName = pickLine(
main,
/^(?:公司名称|单位名称|单位名|名称)\s*[::]\s*(.+)$/
)
const taxNo = pickLine(
main,
/^(?:纳税人识别号|纳税识别号|统一社会信用代码|税\s*号|税号)\s*[::]\s*([0-9A-Za-z]+)\s*$/
)
const bankName = pickLine(
main,
/^(?:银行开户行|开户银行|开户行)\s*[::]\s*(.+)$/
)
let bankAccount = pickLine(
main,
/^(?:银行账号|账\s*号|帐\s*号|账号|帐号)\s*[::]\s*([0-9\s]+)\s*$/
)
if (bankAccount) bankAccount = bankAccount.replace(/\s+/g, '')
let companyAddress = ''
let phoneFax = ''
const addrPhone = pickLine(main, /^(?:地址及电话)\s*[::]\s*(.+)$/)
if (addrPhone) {
const m = addrPhone.match(PHONE_TAIL)
if (m && m.index != null) {
phoneFax = m[1]
companyAddress = normalizeSpaces(addrPhone.slice(0, m.index))
} else {
companyAddress = addrPhone
}
} else {
companyAddress = pickLine(main, /^(?:单位地址|地\s*址|地址)\s*[::]\s*(.+)$/)
}
if (!phoneFax) {
phoneFax =
pickLine(main, /^(?:电话\/传真|电话传真|传真)\s*[::]\s*(.+)$/) ||
pickLine(main, /^(?:电\s*话|电话)\s*[::]\s*(.+)$/)
}
const legalRepresentative = pickLine(
main,
/^(?:法定代表人|法人代表|法人)\s*[::]\s*(.+)$/
)
const agent = pickLine(main, /^(?:委托代理人|代理人)\s*[::]\s*(.+)$/)
const bankCode = pickLine(main, /^(?:银行行号|联行号|行号)\s*[::]\s*([0-9\s]+)\s*$/)
if (bankCode) remarkParts.push(`银行行号:${bankCode.replace(/\s+/g, '')}`)
const zip = pickLine(main, /^(?:邮\s*编|邮编)\s*[::]\s*(\d{6})\s*$/)
if (zip) remarkParts.push(`邮编:${zip}`)
if (mail.trim()) {
const name = pickLine(mail, /^(?:姓\s*名|姓名)\s*[::]\s*(.+)$/)
const phone = pickLine(mail, /^(?:电\s*话|电话|手机)\s*[::]\s*(.+)$/)
const addr = pickLine(mail, /^(?:地\s*址|地址)\s*[::]\s*(.+)$/)
const bits = [name && `收件人 ${name}`, phone, addr].filter(Boolean) as string[]
if (bits.length) remarkParts.push(`邮寄:${bits.join(' / ')}`)
}
const fields = {
companyName: companyName || undefined,
taxNo: taxNo || undefined,
companyAddress: companyAddress || undefined,
legalRepresentative: legalRepresentative || undefined,
agent: agent || undefined,
phoneFax: phoneFax || undefined,
bankName: bankName || undefined,
bankAccount: bankAccount || undefined
}
const filledCount = Object.values(fields).filter(Boolean).length
if (!companyName) warnings.push('未识别到单位名称')
if (!taxNo) warnings.push('未识别到税号')
if (filledCount === 0) warnings.push('未能识别任何开票字段,请检查格式或手动填写')
return { ...fields, remarkParts, warnings, filledCount }
}
export function mergeInvoiceRemark(existing: string | undefined, parts: string[]): string {
const base = (existing || '').trim()
const extras = (parts || []).map((p) => p.trim()).filter(Boolean)
if (!extras.length) return base
let next = base
for (const p of extras) {
if (!next) {
next = p
continue
}
if (next.includes(p)) continue
next = `${next};${p}`
}
return next
}