einvoice-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -0
- package/bin/cli.js +52 -0
- package/index.js +23 -0
- package/lib/BaseInvoiceService.js +158 -0
- package/lib/ErrorHandler.js +98 -0
- package/lib/Invoice.js +108 -0
- package/lib/InvoiceValidator.js +422 -0
- package/lib/OfdInvoiceExtractor.js +170 -0
- package/lib/PDFTextPositionAnalyzer.js +366 -0
- package/lib/PdfFinancialInvoiceService.js +134 -0
- package/lib/PdfFullElectronicInvoiceService.js +325 -0
- package/lib/PdfInvoiceExtractor.js +124 -0
- package/lib/PdfRegularInvoiceService.js +786 -0
- package/lib/RegexPatterns.js +202 -0
- package/lib/StringUtils.js +70 -0
- package/lib/extractor.js +24 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# einvoice-cli
|
|
2
|
+
|
|
3
|
+
电子发票识别 Node.js CLI 工具
|
|
4
|
+
|
|
5
|
+
支持 PDF 和 OFD 两种电子发票格式识别,输出 JSON 格式的发票信息。
|
|
6
|
+
|
|
7
|
+
## 📦 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 🛠️ 使用
|
|
14
|
+
|
|
15
|
+
### 命令行方式
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
node bin/cli.js <pdf_or_ofd_file>
|
|
19
|
+
|
|
20
|
+
# 或安装全局后
|
|
21
|
+
npm install -g .
|
|
22
|
+
einvoice /path/to/invoice.pdf
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 输出示例
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"title": "电子发票(增值税专用发票)",
|
|
30
|
+
"machineNumber": null,
|
|
31
|
+
"code": "35070124",
|
|
32
|
+
"number": "0000600419",
|
|
33
|
+
"date": "2024-12-26",
|
|
34
|
+
"checksum": "EAJfXh",
|
|
35
|
+
"buyerName": "福州猿力信息科技有限公司",
|
|
36
|
+
"buyerCode": null,
|
|
37
|
+
"buyerAddress": null,
|
|
38
|
+
"buyerAccount": null,
|
|
39
|
+
"sellerName": "福建省软件行业协会",
|
|
40
|
+
"sellerCode": null,
|
|
41
|
+
"sellerAddress": null,
|
|
42
|
+
"sellerAccount": null,
|
|
43
|
+
"amount": "1000.00",
|
|
44
|
+
"taxAmount": "0",
|
|
45
|
+
"totalAmount": "1000.00",
|
|
46
|
+
"totalAmountString": "壹仟元整",
|
|
47
|
+
"payee": null,
|
|
48
|
+
"reviewer": null,
|
|
49
|
+
"drawer": "陈榕",
|
|
50
|
+
"password": null,
|
|
51
|
+
"type": "financial",
|
|
52
|
+
"details": [
|
|
53
|
+
{
|
|
54
|
+
"name": "单位会员费",
|
|
55
|
+
"model": null,
|
|
56
|
+
"unit": "元",
|
|
57
|
+
"count": "1",
|
|
58
|
+
"price": "1000.00",
|
|
59
|
+
"amount": "1000.00",
|
|
60
|
+
"taxRate": 0,
|
|
61
|
+
"taxAmount": "0"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 📋 支持的发票类型
|
|
68
|
+
|
|
69
|
+
### 1. 增值税专用发票
|
|
70
|
+
- **识别特征**: 包含"电子发票(增值税专用发票)"字样
|
|
71
|
+
- **关键字段**: `buyerCode`(购方纳税人识别号), `sellerCode`(销方纳税人识别号), `amount`(金额), `taxAmount`(税额), `totalAmount`(价税合计)
|
|
72
|
+
- **示例**: `test4.pdf`
|
|
73
|
+
|
|
74
|
+
### 2. 普通电子发票
|
|
75
|
+
- **识别特征**: 包含"电子发票(普通发票)"字样
|
|
76
|
+
- **关键字段**: 与增值税专用发票类似,税率通常为1%、3%、6%、9%、13%等
|
|
77
|
+
- **特殊格式**: 支持`*餐饮服务*餐费`等包含星号的项目名称
|
|
78
|
+
- **示例**: `test6.pdf`
|
|
79
|
+
|
|
80
|
+
### 3. 福建省财政票据
|
|
81
|
+
- **识别特征**: 包含"福建省社会团体会员费统一收据"字样
|
|
82
|
+
- **关键字段**: `code`(票据代码), `number`(票据号码), `checksum`(校验码), `amount`(金额), `totalAmountString`(大写金额)
|
|
83
|
+
- **特点**: 无税额(`taxAmount`为0),明细格式特殊
|
|
84
|
+
- **示例**: `test8.pdf`
|
|
85
|
+
|
|
86
|
+
### 4. 其他发票类型
|
|
87
|
+
- **通行费发票**: 包含"通行费"和"车牌号"字样
|
|
88
|
+
- **OFD格式发票**: 支持OFD文件格式的电子发票
|
|
89
|
+
|
|
90
|
+
## ✨ 特性
|
|
91
|
+
|
|
92
|
+
- ✅ 多类型发票识别(增值税专用发票、普通发票、财政票据等)
|
|
93
|
+
- ✅ PDF 和 OFD 双格式支持
|
|
94
|
+
- ✅ JSON 格式输出,便于程序处理
|
|
95
|
+
- ✅ 字段验证和自动矫正
|
|
96
|
+
- ✅ 坐标定位精确提取
|
|
97
|
+
- ✅ 多策略降级确保高成功率
|
|
98
|
+
- ✅ 无界面,轻量级依赖
|
|
99
|
+
|
|
100
|
+
## 📚 API 使用
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const { extractPdf, extractOfd } = require('./lib/extractor');
|
|
104
|
+
|
|
105
|
+
// 提取 PDF 发票
|
|
106
|
+
const invoice = await extractPdf('./invoice.pdf');
|
|
107
|
+
console.log(JSON.stringify(invoice, null, 2));
|
|
108
|
+
|
|
109
|
+
// 提取 OFD 发票
|
|
110
|
+
const invoice = await extractOfd('./invoice.ofd');
|
|
111
|
+
console.log(JSON.stringify(invoice, null, 2));
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 🏗️ 项目结构
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
einvoice-nodejs/
|
|
118
|
+
├── bin/
|
|
119
|
+
│ └── cli.js # 命令行入口
|
|
120
|
+
├── lib/
|
|
121
|
+
│ ├── extractor.js # 主提取器
|
|
122
|
+
│ ├── Invoice.js # 数据模型
|
|
123
|
+
│ ├── RegexPatterns.js # 正则优化库
|
|
124
|
+
│ ├── PDFTextPositionAnalyzer.js # 坐标定位引擎
|
|
125
|
+
│ ├── InvoiceValidator.js # 验证框架
|
|
126
|
+
│ ├── PdfRegularInvoiceService.js # 普通发票服务
|
|
127
|
+
│ ├── PdfFullElectronicInvoiceService.js # 全电发票服务
|
|
128
|
+
│ ├── PdfFinancialInvoiceService.js # 财政票据服务
|
|
129
|
+
│ ├── PdfInvoiceExtractor.js # PDF 加载器
|
|
130
|
+
│ ├── OfdInvoiceExtractor.js # OFD 提取器
|
|
131
|
+
│ └── StringUtils.js # 字符串工具
|
|
132
|
+
├── package.json
|
|
133
|
+
├── README.md # 项目文档
|
|
134
|
+
└── .gitignore
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 📄 许可证
|
|
138
|
+
|
|
139
|
+
MIT License
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { extract } = require('../lib/extractor');
|
|
7
|
+
|
|
8
|
+
const packageJson = require('../package.json');
|
|
9
|
+
|
|
10
|
+
program.version(packageJson.version).description(packageJson.description);
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.argument('<file>', 'PDF or OFD invoice file path')
|
|
14
|
+
.action(async (filePath) => {
|
|
15
|
+
try {
|
|
16
|
+
// 检查文件是否存在
|
|
17
|
+
if (!fs.existsSync(filePath)) {
|
|
18
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 检查文件格式
|
|
23
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
24
|
+
if (!['.pdf', '.ofd'].includes(ext)) {
|
|
25
|
+
console.error('Error: Unsupported file format. Only PDF and OFD are supported.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
// 提取发票信息
|
|
31
|
+
const invoice = await extract(filePath);
|
|
32
|
+
|
|
33
|
+
// 输出 JSON
|
|
34
|
+
const jsonOutput = invoice.toJSON();
|
|
35
|
+
// 如果发票有错误信息,也输出
|
|
36
|
+
if (invoice.extractionError) {
|
|
37
|
+
jsonOutput.extractionError = invoice.extractionError;
|
|
38
|
+
}
|
|
39
|
+
if (invoice.error) {
|
|
40
|
+
jsonOutput.error = invoice.error;
|
|
41
|
+
}
|
|
42
|
+
if (invoice.errorStack) {
|
|
43
|
+
jsonOutput.errorStack = invoice.errorStack;
|
|
44
|
+
}
|
|
45
|
+
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error:', error.message);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
program.parse();
|
package/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* index.js - 主入口模块
|
|
3
|
+
* 可以直接在 Node.js 中导入使用
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { extract, extractPdf, extractOfd } = require('./lib/extractor');
|
|
7
|
+
const { Invoice, Detail } = require('./lib/Invoice');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
// 主要 API
|
|
11
|
+
extract, // 自动识别文件格式
|
|
12
|
+
extractPdf, // 专门处理 PDF
|
|
13
|
+
extractOfd, // 专门处理 OFD
|
|
14
|
+
|
|
15
|
+
// 数据模型(如果需要扩展)
|
|
16
|
+
Invoice,
|
|
17
|
+
Detail,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// 使用示例
|
|
21
|
+
// const einvoice = require('./index');
|
|
22
|
+
// const invoice = await einvoice.extract('./invoice.pdf');
|
|
23
|
+
// console.log(invoice.toJSON());
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const { Invoice, Detail } = require('./Invoice');
|
|
2
|
+
const RegexPatterns = require('./RegexPatterns');
|
|
3
|
+
const InvoiceValidator = require('./InvoiceValidator');
|
|
4
|
+
const ErrorHandler = require('./ErrorHandler');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 发票服务基类
|
|
8
|
+
* 提取三个发票服务类的公共方法和逻辑
|
|
9
|
+
*/
|
|
10
|
+
class BaseInvoiceService {
|
|
11
|
+
/**
|
|
12
|
+
* 提取基础字段 - 通用实现
|
|
13
|
+
* @param {Invoice} invoice - 发票对象
|
|
14
|
+
* @param {string} allText - 规范化后的文本
|
|
15
|
+
* @param {Object} options - 配置选项
|
|
16
|
+
*/
|
|
17
|
+
static extractBasicFields(invoice, allText, options = {}) {
|
|
18
|
+
const patterns = options.patterns || RegexPatterns.BASIC_FIELDS;
|
|
19
|
+
|
|
20
|
+
for (const [key, pattern] of Object.entries(patterns)) {
|
|
21
|
+
const result = RegexPatterns.tryPatterns(allText, [pattern]);
|
|
22
|
+
if (result) {
|
|
23
|
+
invoice[key] = result.match[1] || result.match[0];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 提取人员信息(签单人、收款人、复核人)
|
|
30
|
+
* @param {Invoice} invoice - 发票对象
|
|
31
|
+
* @param {string} allText - 规范化后的文本
|
|
32
|
+
*/
|
|
33
|
+
static extractPersonInfo(invoice, allText) {
|
|
34
|
+
// 开票人 - 匹配直到遇到关键词或标点符号
|
|
35
|
+
const drawerMatch = allText.match(/开票人[::]\s*([^销售方复核收款人,,。;;\n\r]+?)(?=[销售方复核收款人,,。;;\n\r]|$)/);
|
|
36
|
+
if (drawerMatch) {
|
|
37
|
+
invoice.drawer = drawerMatch[1].trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 收款人/收款人
|
|
41
|
+
const collectorMatch = allText.match(/收款人[::]\s*([^开票人复核销售方,,。;;\n\r]+?)(?=[开票人复核销售方,,。;;\n\r]|$)/);
|
|
42
|
+
if (collectorMatch) {
|
|
43
|
+
invoice.collector = collectorMatch[1].trim();
|
|
44
|
+
// 同时设置 payee 字段
|
|
45
|
+
invoice.payee = collectorMatch[1].trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 复核/复核人
|
|
49
|
+
const reviewerMatch = allText.match(/复核人?[::]\s*([^开票人收款人销售方,,。;;\n\r]+?)(?=[开票人收款人销售方,,。;;\n\r]|$)/);
|
|
50
|
+
if (reviewerMatch) {
|
|
51
|
+
invoice.reviewer = reviewerMatch[1].trim();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 验证发票数据
|
|
57
|
+
* @param {Invoice} invoice - 发票对象
|
|
58
|
+
*/
|
|
59
|
+
static validateInvoice(invoice) {
|
|
60
|
+
const validation = InvoiceValidator.validate(invoice);
|
|
61
|
+
invoice.validationResult = {
|
|
62
|
+
valid: validation.valid,
|
|
63
|
+
warnings: validation.warnings,
|
|
64
|
+
errors: validation.errors,
|
|
65
|
+
suggestions: validation.suggestions
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// 修正常见错误
|
|
69
|
+
if (validation.suggestions && validation.suggestions.length > 0) {
|
|
70
|
+
InvoiceValidator.correctCommonErrors(invoice);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 安全执行提取操作
|
|
76
|
+
* @param {Function} extractFn - 提取函数
|
|
77
|
+
* @param {Array} args - 函数参数
|
|
78
|
+
* @returns {Invoice} 发票对象
|
|
79
|
+
*/
|
|
80
|
+
static safeExtract(extractFn, ...args) {
|
|
81
|
+
return ErrorHandler.safeExtract(extractFn, args, 'invoice-service');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 清理文本中的控制字符和特殊字符
|
|
86
|
+
* @param {string} text - 原始文本
|
|
87
|
+
* @returns {string} 清理后的文本
|
|
88
|
+
*/
|
|
89
|
+
static cleanText(text) {
|
|
90
|
+
if (!text) return '';
|
|
91
|
+
|
|
92
|
+
// 移除控制字符
|
|
93
|
+
let cleaned = text.replace(/[\x00-\x1F\x7F]/g, '');
|
|
94
|
+
|
|
95
|
+
// 移除多余的空格和换行
|
|
96
|
+
cleaned = cleaned.replace(/\s+/g, ' ').trim();
|
|
97
|
+
|
|
98
|
+
return cleaned;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 提取日期信息
|
|
103
|
+
* @param {string} text - 文本
|
|
104
|
+
* @returns {string|null} 日期字符串
|
|
105
|
+
*/
|
|
106
|
+
static extractDate(text) {
|
|
107
|
+
const datePatterns = [
|
|
108
|
+
RegexPatterns.BASIC_FIELDS.date,
|
|
109
|
+
RegexPatterns.BASIC_FIELDS.dateDash,
|
|
110
|
+
RegexPatterns.BASIC_FIELDS.dateCompact,
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
for (const pattern of datePatterns) {
|
|
114
|
+
const match = text.match(pattern);
|
|
115
|
+
if (match) {
|
|
116
|
+
return match[1];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 提取纳税人识别号
|
|
125
|
+
* @param {string} text - 文本
|
|
126
|
+
* @returns {Array} 识别号数组
|
|
127
|
+
*/
|
|
128
|
+
static extractTaxIds(text) {
|
|
129
|
+
const matches = RegexPatterns.getAllMatches(
|
|
130
|
+
text,
|
|
131
|
+
/纳税人识别号[::\s]*([\dA-Z]{14,20})/g
|
|
132
|
+
);
|
|
133
|
+
return matches.map(match => match[1]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 提取购销方名称
|
|
138
|
+
* @param {string} text - 文本
|
|
139
|
+
* @returns {Object} 包含buyerName和sellerName的对象
|
|
140
|
+
*/
|
|
141
|
+
static extractPartyNames(text) {
|
|
142
|
+
const result = { buyerName: null, sellerName: null };
|
|
143
|
+
|
|
144
|
+
const buyerMatch = text.match(/购.*?名称[::\s]*([^\n\r]+?)(?=\s*销|$)/);
|
|
145
|
+
if (buyerMatch) {
|
|
146
|
+
result.buyerName = buyerMatch[1].trim().replace(/\s+/g, ' ');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const sellerMatch = text.match(/销.*?名称[::\s]*([^\n\r]+?)(?=\s*(?:买售方方信|统一社会信用代码|纳税人识别号|项目名称|$))/);
|
|
150
|
+
if (sellerMatch) {
|
|
151
|
+
result.sellerName = sellerMatch[1].trim().replace(/\s+/g, ' ');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = BaseInvoiceService;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const { Invoice } = require('./Invoice');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 统一错误处理工具类
|
|
5
|
+
*/
|
|
6
|
+
class ErrorHandler {
|
|
7
|
+
/**
|
|
8
|
+
* 创建错误发票对象
|
|
9
|
+
* @param {Error|string} error - 错误对象或错误消息
|
|
10
|
+
* @param {string} source - 错误来源(如 'pdf', 'ofd', 'extractor')
|
|
11
|
+
* @returns {Invoice} 包含错误信息的发票对象
|
|
12
|
+
*/
|
|
13
|
+
static createErrorInvoice(error, source = 'unknown') {
|
|
14
|
+
const errorInvoice = new Invoice();
|
|
15
|
+
errorInvoice.title = 'error';
|
|
16
|
+
errorInvoice.type = 'error';
|
|
17
|
+
errorInvoice.extractionError = typeof error === 'string' ? error : error.message;
|
|
18
|
+
errorInvoice.errorSource = source;
|
|
19
|
+
|
|
20
|
+
// 保留堆栈信息用于调试
|
|
21
|
+
if (error instanceof Error && error.stack) {
|
|
22
|
+
errorInvoice.errorStack = error.stack;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 设置验证结果为无效
|
|
26
|
+
errorInvoice.validationResult = {
|
|
27
|
+
valid: false,
|
|
28
|
+
warnings: [],
|
|
29
|
+
errors: [`提取失败: ${errorInvoice.extractionError}`],
|
|
30
|
+
suggestions: ['请检查文件格式是否正确']
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return errorInvoice;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 安全执行提取操作
|
|
38
|
+
* @param {Function} extractFn - 提取函数
|
|
39
|
+
* @param {Array} args - 函数参数
|
|
40
|
+
* @param {string} source - 错误来源
|
|
41
|
+
* @returns {Invoice} 发票对象(成功或错误)
|
|
42
|
+
*/
|
|
43
|
+
static safeExtract(extractFn, args = [], source = 'extractor') {
|
|
44
|
+
try {
|
|
45
|
+
return extractFn(...args);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(`${source} 提取异常:`, error.message);
|
|
48
|
+
return this.createErrorInvoice(error, source);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 检查是否为错误发票
|
|
54
|
+
* @param {Invoice} invoice - 发票对象
|
|
55
|
+
* @returns {boolean} 是否为错误发票
|
|
56
|
+
*/
|
|
57
|
+
static isErrorInvoice(invoice) {
|
|
58
|
+
return invoice && (
|
|
59
|
+
invoice.title === 'error' ||
|
|
60
|
+
invoice.type === 'error' ||
|
|
61
|
+
!!invoice.extractionError ||
|
|
62
|
+
!!invoice.error
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 获取错误信息
|
|
68
|
+
* @param {Invoice} invoice - 发票对象
|
|
69
|
+
* @returns {string|null} 错误信息
|
|
70
|
+
*/
|
|
71
|
+
static getErrorMessage(invoice) {
|
|
72
|
+
if (!this.isErrorInvoice(invoice)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return invoice.extractionError || invoice.error || '未知错误';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 统一格式化错误输出
|
|
81
|
+
* @param {Invoice} invoice - 发票对象
|
|
82
|
+
* @returns {Object} 格式化的错误信息
|
|
83
|
+
*/
|
|
84
|
+
static formatError(invoice) {
|
|
85
|
+
if (!this.isErrorInvoice(invoice)) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
error: this.getErrorMessage(invoice),
|
|
92
|
+
source: invoice.errorSource || 'unknown',
|
|
93
|
+
timestamp: new Date().toISOString()
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = ErrorHandler;
|
package/lib/Invoice.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 发票类
|
|
3
|
+
*/
|
|
4
|
+
class Invoice {
|
|
5
|
+
constructor() {
|
|
6
|
+
// 基础信息
|
|
7
|
+
this.title = null;
|
|
8
|
+
this.type = null;
|
|
9
|
+
|
|
10
|
+
// 发票编号相关
|
|
11
|
+
this.machineNumber = null;
|
|
12
|
+
this.code = null;
|
|
13
|
+
this.number = null;
|
|
14
|
+
this.date = null;
|
|
15
|
+
this.checksum = null;
|
|
16
|
+
|
|
17
|
+
// 购买方信息
|
|
18
|
+
this.buyerName = null;
|
|
19
|
+
this.buyerCode = null;
|
|
20
|
+
this.buyerAddress = null;
|
|
21
|
+
this.buyerAccount = null;
|
|
22
|
+
|
|
23
|
+
// 销售方信息
|
|
24
|
+
this.sellerName = null;
|
|
25
|
+
this.sellerCode = null;
|
|
26
|
+
this.sellerAddress = null;
|
|
27
|
+
this.sellerAccount = null;
|
|
28
|
+
|
|
29
|
+
// 金额相关
|
|
30
|
+
this.amount = null;
|
|
31
|
+
this.taxAmount = null;
|
|
32
|
+
this.totalAmount = null;
|
|
33
|
+
this.totalAmountString = null;
|
|
34
|
+
|
|
35
|
+
// 签章信息
|
|
36
|
+
this.payee = null;
|
|
37
|
+
this.reviewer = null;
|
|
38
|
+
this.drawer = null;
|
|
39
|
+
this.password = null;
|
|
40
|
+
|
|
41
|
+
// 明细
|
|
42
|
+
this.details = [];
|
|
43
|
+
|
|
44
|
+
// 错误信息
|
|
45
|
+
this.error = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
toJSON() {
|
|
49
|
+
return {
|
|
50
|
+
title: this.title,
|
|
51
|
+
machineNumber: this.machineNumber,
|
|
52
|
+
code: this.code,
|
|
53
|
+
number: this.number,
|
|
54
|
+
date: this.date,
|
|
55
|
+
checksum: this.checksum,
|
|
56
|
+
buyerName: this.buyerName,
|
|
57
|
+
buyerCode: this.buyerCode,
|
|
58
|
+
buyerAddress: this.buyerAddress,
|
|
59
|
+
buyerAccount: this.buyerAccount,
|
|
60
|
+
sellerName: this.sellerName,
|
|
61
|
+
sellerCode: this.sellerCode,
|
|
62
|
+
sellerAddress: this.sellerAddress,
|
|
63
|
+
sellerAccount: this.sellerAccount,
|
|
64
|
+
amount: this.amount,
|
|
65
|
+
taxAmount: this.taxAmount,
|
|
66
|
+
totalAmount: this.totalAmount,
|
|
67
|
+
totalAmountString: this.totalAmountString,
|
|
68
|
+
payee: this.payee,
|
|
69
|
+
reviewer: this.reviewer,
|
|
70
|
+
drawer: this.drawer,
|
|
71
|
+
password: this.password,
|
|
72
|
+
type: this.type,
|
|
73
|
+
details: this.details,
|
|
74
|
+
...(this.error && { error: this.error }),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 发票明细类
|
|
81
|
+
*/
|
|
82
|
+
class Detail {
|
|
83
|
+
constructor() {
|
|
84
|
+
this.name = '';
|
|
85
|
+
this.model = null;
|
|
86
|
+
this.unit = null;
|
|
87
|
+
this.count = null;
|
|
88
|
+
this.price = null;
|
|
89
|
+
this.amount = null;
|
|
90
|
+
this.taxRate = null;
|
|
91
|
+
this.taxAmount = null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
toJSON() {
|
|
95
|
+
return {
|
|
96
|
+
name: this.name,
|
|
97
|
+
model: this.model,
|
|
98
|
+
unit: this.unit,
|
|
99
|
+
count: this.count,
|
|
100
|
+
price: this.price,
|
|
101
|
+
amount: this.amount,
|
|
102
|
+
taxRate: this.taxRate,
|
|
103
|
+
taxAmount: this.taxAmount,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { Invoice, Detail };
|