人财事物信息化 - purchase_invoice.py
以下是ERPNext采购发票模块的Python代码详细分析:
- 核心类结构
- 继承自
BuyingController
,处理采购业务流程 - 包含采购发票的完整生命周期管理:创建、验证、提交、取消
- 初始化与状态管理
__init__
初始化状态更新器,跟踪采购订单项状态status_updater
配置:- 更新采购订单项计费金额和百分比
- 处理库存接收数量更新
- 退货数量跟踪
- 验证体系
- 基础验证:
- 必填项检查(供应商、日期等)
- 单据状态验证(是否冻结/关闭)
- 货币汇率验证
- 业务规则验证:
- 采购订单/收货单强制要求(根据系统配置)
- 库存更新时的仓库验证
- 供应商发票唯一性校验
- 预付款与核销金额匹配
- 财务校验:
- 费用科目有效性
- 税务账户配置
- 暂记账户处理
- 会计处理
- 总账分录:
- 供应商科目处理(应付账款)
- 库存科目调整(永续库存)
- 税款分离处理(进项税、代扣税)
- 汇兑损益计算
- 特殊处理:
- 内部转移交易处理
- 资产类目(CWIP)特殊科目
- 暂估入库处理
- 付款相关:
- 现金/银行科目处理
- 预付款分配
- 坏账核销处理
- 库存管理
- 库存更新逻辑:
- 库存价值调整
- 批次/序列号跟踪
- 供应商仓库特殊处理
- 关联文档更新:
- 采购订单计费状态
- 收货单接收数量
- 物料请求状态
- 税务处理
- 代扣所得税(TDS):
- 供应商级税务配置
- 预付款税款分配
- 多凭证税款跟踪
- 增值税处理:
- 进项税额分离
- 不同税率处理
- 南非特殊处理
- 高级功能
- 项目成本跟踪:
- 自动更新项目采购成本
- 多项目分配处理
- 内部交易:
- 公司间交易处理
- 未实现损益计算
- 暂记处理:
- 暂估费用科目
- 库存暂估调整
- 资产处理:
- 固定资产采购
- 在建工程核算
- 工作流控制
- 单据冻结/解冻:
- 冻结原因记录
- 解冻日期控制
- 审批流程:
- 授权金额校验
- 修改跟踪
- 关联文档状态同步:
- 采购订单
- 收货单
- 付款凭证
- 取消与冲销
- 库存反向处理
- 会计凭证冲销
- 项目成本回滚
- 关联状态恢复
- 税款分配撤销
- 特殊业务场景
- 退货处理:
- 反向库存移动
- 红字发票处理
- 关联文档逆向更新
- 暂估入库:
- 临时科目处理
- 正式发票匹配
- 代采购处理:
- 供应商仓库特殊流程
- 代购费用分配
- 性能优化
- 批量处理:
- 库存条目批量更新
- 项目成本批量计算
- 智能合并:
- 相似总账分录合并
- 重复校验优化
- 异步处理:
- 未来库存重算
- 延迟过账处理
- 国际化支持
- 多币种处理:
- 本位币转换
- 外币支付处理
- 区域化定制:
- 南非增值税处理
- 不同国家税务模板
- 扩展性设计
- 钩子函数:
- 验证扩展点
- 状态更新扩展
- 模块化设计:
- 税务组件分离
- 库存组件解耦
- 配置驱动:
- 公司级默认科目
- 系统参数控制流程
该模块完整实现了采购发票的全生命周期管理,深度集成了财务、库存、采购、项目等多个模块,体现了ERPNext高度模块化和集成化的设计理念。通过严谨的状态管理和完善的验证体系,确保业务数据的完整性和准确性。
根据上传的发票明细直接创建对应采购发票(同时创建缺失的供应商和商品服务)
以下是针对ERPNext采购发票模块的本地化增强方案,实现通过发票明细文件自动创建采购发票及关联主数据的功能:
一、功能架构设计
- 文件处理层
class InvoiceFileParser:
SUPPORTED_FORMATS = {'csv', 'xlsx'}
def parse(self, file_path):
ext = file_path.split('.')-1
if ext == 'csv':
return self._parse_csv(file_path)
elif ext == 'xlsx':
return self._parse_excel(file_path)
def _parse_csv(self, file_path):
实现CSV解析逻辑
pass
def _parse_excel(self, file_path):
实现Excel解析逻辑
pass
- 数据映射层
class FieldMapper:
DEFAULT_MAPPING = {
'supplier_name': '供应商名称',
'tax_id': '税号',
'item_code': '商品编码',
'qty': '数量'
}
def __init__(self, custom_mapping=None):
self.mapping = {self.DEFAULT_MAPPING, (custom_mapping or {})}
def transform(self, raw_data):
执行字段转换和清洗
pass
- 业务处理层
class InvoiceCreator:
def create_invoices(self, processed_data):
invoices =
for record in processed_data:
self._create_missing_master(record)
pi = self._build_invoice(record)
pi.insert()
invoices.append(pi)
return invoices
def _create_missing_master(self, record):
if not frappe.db.exists('Supplier', record'supplier_name'):
self._create_supplier(record)
if not frappe.db.exists('Item', record'item_code'):
self._create_item(record)
def _create_supplier(self, data):
supplier = frappe.new_doc('Supplier')
supplier.update({
'supplier_name': data'supplier_name',
'tax_id': data.get('tax_id'),
'supplier_type': '本地供应商'
})
supplier.insert()
def _create_item(self, data):
item = frappe.new_doc('Item')
item.update({
'item_code': data'item_code',
'item_name': data.get('item_name'),
'item_group': '服务类',
'is_purchase_item': 1
})
item.insert()
二、 核心功能实现
- 智能供应商匹配
def _match_supplier(self, record):
多维度匹配逻辑
match_conditions =
('tax_id', '=', record.get('tax_id')),
('mobile_no', '=', record.get('contact_phone')),
('supplier_name', 'like', f"%{record'supplier_name'}%")
for field, op, val in match_conditions:
if val:
suppliers = frappe.get_all('Supplier',
filters={field: op, val},
fields='name'
)
if suppliers:
return suppliers0.name
return None
- 自动化商品创建
def _create_item(self, data):
item = frappe.new_doc('Item')
defaults = {
'description': data.get('item_desc'),
'stock_uom': data.get('uom', '个'),
'is_stock_item': 0,
'taxes': {
'item_tax_template': self._get_tax_template(data.get('tax_rate'))
}
}
item.update({data, defaults})
item.insert()
- 发票生成引擎
def _build_invoice(self, record):
pi = frappe.new_doc('Purchase Invoice')
pi.update({
'supplier': record'supplier',
'posting_date': record'invoice_date',
'due_date': self._calculate_due_date(record),
'items': {
'item_code': record'item_code',
'qty': record'qty',
'rate': record'unit_price',
'cost_center': '主成本中心'
},
'taxes_and_charges': self._get_tax_rule(record)
})
pi.run_method('set_missing_values')
return pi
三、异常处理机制
- 数据校验流水线
class DataValidator:
REQUIRED_FIELDS = 'supplier_name', 'item_code', 'total_amount'
def validate(self, records):
errors =
for idx, rec in enumerate(records):
if not all(rec.get(f) for f in self.REQUIRED_FIELDS):
errors.append(f"行{idx+1}: 缺少必填字段")
if rec.get('tax_id') and not self._validate_tax_id(rec'tax_id'):
errors.append(f"行{idx+1}: 税号格式错误")
return errors
- 事务回滚机制
def safe_import(file_path):
try:
data = parse_file(file_path)
errors = validate(data)
if errors:
raise ValidationError("\n".join(errors))
frappe.enqueue(
'_execute_import',
data=data,
timeout=3600
)
except Exception as e:
frappe.log_error("发票导入失败")
rollback_transaction()
四、用户界面增强
- 文件上传组件
frappe.ui.form.on('Bulk Invoice Import', {
upload_complete: function(frm, attachment) {
frm.call({
method: 'process_uploaded_file',
args: {
file_url: attachment.file_url
},
callback: function(r) {
frm.refresh_field('import_results')
}
})
}
})
- 映射配置界面
<div class="form-group">
<label>供应商名称映射:</label>
<select class="form-control" data-fieldname="supplier_mapping">
<option value="vendor_name">供应商名称</option>
<option value="company_name">公司名称</option>
</select>
</div>
五、部署配置项
- 参数配置文件
class LocalConfig:
AUTO_CREATE_MASTER = True
DEFAULT_TAX_RATE = 0.13
ALLOWED_FILE_SIZE = 50 MB
REQUIRED_TAX_FIELDS = 'tax_id', 'bank_account'
- 定时任务配置
python
scheduler.add_job(
process_pending_invoices,
'cron',
hour=2,
kwargs={'batch_size': 100}
)
六、安全增强措施
- 文件安全扫描
def check_file_safety(file_path):
if not antivirus_scan(file_path):
raise SecurityException("文件包含恶意代码")
if detect_script_tags(file_path):
raise ValidationError("文件包含非法脚本")
- 访问控制
def check_permission():
if not frappe.has_permission('Purchase Invoice', 'create'):
frappe.throw("无权限执行此操作", frappe.PermissionError)
七、技术验证点
- 性能基准测试
class PerformanceTest:
@benchmark
def test_1000_records(self):
data = generate_test_data(1000)
creator = InvoiceCreator()
return creator.create_invoices(data)
- 数据一致性检查
def verify_integrity(original_data, created_invoices):
total_amount = sum(d'total' for d in original_data)
system_total = sum(pi.grand_total for pi in created_invoices)
assert abs(total_amount - system_total) < 0.01
该方案实现了从发票文件解析到系统创建的完整自动化流程,通过模块化设计保证扩展性,结合异常处理和事务机制确保数据安全,适用于处理大批量发票数据录入场景。实施时需注意:
- 供应商匹配算法的优化(可引入模糊匹配)
- 商品税率逻辑与当地税务政策保持同步
- 定期清理临时文件
- 建立操作日志审计追踪
- 提供数据修正界面处理导入异常