区分正式付款回单、非最终回执与未知凭证,在核验构建、人工证据重建和最终核销时使用同一份有效依据。非最终说明、入账方向、状态矛盾及重复交易不能经人工覆盖。 正式回单无字面成功状态时,允许财务按原件实际记账支出、日期及说明提出核验,由本批次适用的确认人确认;不补造银行成功文字。旧核验可显式本地重检,新版本与审计原子保存,不重新外发 OCR,不沿用旧人工批准或改写已核销历史。 本地验证:82 个迁移、借款 HTTP 116 组、关联电脑/手机 18 组、共享回单数据库与真实认证 38 组、共享报销页面限定重验 24 项、最新回单/报销定向 292 项通过;9 个资金守卫变异均被捕获。服务端全量 4554 项通过、1 项既有可选跳过,后续合成数据及零页详情改动由最新定向验证覆盖。 本次无新增迁移,新增本地 PDF 文本解析依赖。后续对本提交运行完整 verify-candidate,实际候选与结果以本轮回执为准。 限制:独立代码复核及真实还款验收待完成;历史材料预验不证明真实还本或银行提交对应。未推送、未部署、未访问生产或写入真实业务数据。
47 行
3.0 KiB
JavaScript
47 行
3.0 KiB
JavaScript
const { createRequire } = require('node:module');
|
|
const path = require('node:path');
|
|
const req = createRequire(path.resolve(__dirname, '../../server/package.json'));
|
|
|
|
function syntheticReceiptText({ status = 'success', final = true, direction = 'debit', amount = '10.00',
|
|
currency = '人民币', payerAccount = '000011112222', payeeAccount = '000033334444',
|
|
reference = 'SYNTHETIC-REFERENCE', bookedOn = '2026-10-01' } = {}) {
|
|
const words = { success: '交易成功', failed: '交易失败', pending: '处理中', booked: '' };
|
|
return [final ? '中国建设银行单位客户专用回单' : '中国建设银行网上银行电子回执',
|
|
'合成公司测试回单 SYNTHETIC', `记账日期 ${bookedOn}`, direction === 'credit' ? '贷' : '借',
|
|
`金额 ${amount}`, `币别 ${currency}`, `付款人账号 ${payerAccount}`, `收款人账号 ${payeeAccount}`, `凭证号码 ${reference}`,
|
|
...(words[status] ? [`单据状态 ${words[status]}`] : []),
|
|
...(!final ? ['本回执不作为收、付款方交易的最终依据,正式回单请在交易成功后另打印。'] : [])].join('\n');
|
|
}
|
|
|
|
async function syntheticBankReceiptPdf({ pages = 1, tag = 'SYNTHETIC', ...options } = {}) {
|
|
const { PDFDocument, PDFHexString, beginText, endText, setFontAndSize, moveText, showText } = req('pdf-lib');
|
|
const doc = await PDFDocument.create(); doc.setTitle(tag);
|
|
const text = syntheticReceiptText(options);
|
|
const hex = (s) => Buffer.from(s, 'utf16le').swap16().toString('hex').toUpperCase();
|
|
const codes = [...new Set([...text.replace(/\n/g, '')])].map((c) => `<${hex(c)}> <${hex(c)}>`);
|
|
const cmap = `/CIDInit /ProcSet findresource begin 12 dict begin begincmap
|
|
/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def
|
|
/CMapName /Synthetic-UCS def /CMapType 2 def
|
|
1 begincodespacerange <0000> <FFFF> endcodespacerange
|
|
${codes.length} beginbfchar\n${codes.join('\n')}\nendbfchar
|
|
endcmap CMapName currentdict /CMap defineresource pop end end`;
|
|
const toUnicode = doc.context.register(doc.context.flateStream(cmap));
|
|
const cid = doc.context.register(doc.context.obj({ Type: 'Font', Subtype: 'CIDFontType0', BaseFont: 'STSong-Light',
|
|
CIDSystemInfo: { Registry: PDFHexString.fromText('Adobe'), Ordering: PDFHexString.fromText('GB1'), Supplement: 4 }, DW: 1000 }));
|
|
const font = doc.context.register(doc.context.obj({ Type: 'Font', Subtype: 'Type0', BaseFont: 'STSong-Light',
|
|
Encoding: 'Identity-H', DescendantFonts: [cid], ToUnicode: toUnicode }));
|
|
for (let n = 0; n < pages; n++) {
|
|
const page = doc.addPage([595, 842]);
|
|
const name = page.node.newFontDictionary('SYN', font);
|
|
text.split('\n').forEach((line, index) => page.pushOperators(beginText(), setFontAndSize(name, 10),
|
|
moveText(30, 790 - index * 22), showText(PDFHexString.of(hex(line))), endText()));
|
|
}
|
|
return Buffer.from(await doc.save());
|
|
}
|
|
|
|
function syntheticDocumentEvidence(options) {
|
|
return req('./src/services/paymentReceiptDocument').classifyReceiptText(syntheticReceiptText(options));
|
|
}
|
|
|
|
module.exports = { syntheticReceiptText, syntheticBankReceiptPdf, syntheticDocumentEvidence };
|