别再手动操作了!用Python脚本批量处理SAP发票(MIR7/MIRO/MR8M)的自动化方案
·
用Python解放双手:SAP发票自动化处理实战指南
每月底财务部门的同事总在重复相同的操作——打开SAP,输入MIR7/MIRO/MR8M事务码,逐个处理成百上千张发票的删除或冲销请求。这种机械式操作不仅效率低下,还容易因疲劳导致误操作。其实,只需几行Python代码,就能将这些重复劳动交给机器自动完成。
1. 环境准备与SAP连接配置
1.1 安装必要Python库
处理SAP自动化需要两个核心库:
pyrfc
用于RFC通信,
pandas
用于数据处理。建议使用conda创建独立环境:
conda create -n sap_auto python=3.8
conda activate sap_auto
pip install pyrfc pandas openpyxl
注意:pyrfc需要SAP NWRFC SDK支持,需提前从SAP官网下载对应版本
1.2 SAP连接参数配置
建立可靠的RFC连接是自动化基础。建议将连接参数保存在配置文件中:
# config.ini
[SAP_PROD]
ashost=sap.example.com
sysnr=00
client=100
user=automation_user
passwd=secure_password
lang=EN
连接测试代码:
from pyrfc import Connection
conn = Connection(config_section='SAP_PROD')
print(conn.get_system_info()) # 验证连接
2. 发票处理核心BAPI解析
2.1 MIR7预制发票删除
BAPI_INCOMINGINVOICE_DELETE
需要两个关键参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| invoicedocnumber | CHAR(10) | 发票凭证号 |
| fiscalyear | CHAR(4) | 会计年度 |
典型错误处理模式:
def delete_invoice(doc_number, fiscal_year):
try:
# 格式转换
doc_number = str(doc_number).zfill(10)
result = conn.call('BAPI_INCOMINGINVOICE_DELETE',
invoicedocnumber=doc_number,
fiscalyear=fiscal_year)
if any(msg['TYPE'] == 'E' for msg in result['RETURN']):
conn.call('BAPI_TRANSACTION_ROLLBACK')
return False, [msg for msg in result['RETURN'] if msg['TYPE'] in ('E', 'A')]
else:
conn.call('BAPI_TRANSACTION_COMMIT', wait=True)
return True, f"预制发票 {doc_number} 删除成功"
except Exception as e:
return False, str(e)
2.2 MIRO发票冲销
MR8M事务对应的
BAPI_INCOMINGINVOICE_CANCEL
更复杂:
def cancel_invoice(doc_number, fiscal_year, reason_code='03', post_date=None):
params = {
'invoicedocnumber': str(doc_number).zfill(10),
'fiscalyear': fiscal_year,
'reasonreversal': reason_code
}
if reason_code == '04' and post_date:
params['postingdate'] = post_date.strftime('%Y%m%d')
result = conn.call('BAPI_INCOMINGINVOICE_CANCEL', **params)
if result.get('invoicedocnumber_reversal'):
conn.call('BAPI_TRANSACTION_COMMIT', wait=True)
return True, {
'new_doc': result['invoicedocnumber_reversal'],
'new_year': result['fiscalyear_reversal']
}
else:
conn.call('BAPI_TRANSACTION_ROLLBACK')
return False, [msg for msg in result['RETURN'] if msg['TYPE'] in ('E', 'A')]
3. 构建自动化处理流水线
3.1 从Excel读取任务清单
典型任务清单格式示例:
| 操作类型 | 凭证号 | 年度 | 冲销原因 | 过账日期 |
|---|---|---|---|---|
| DELETE | 5100001234 | 2023 | - | - |
| CANCEL | 5100005678 | 2023 | 03 | - |
处理脚本:
import pandas as pd
def process_batch(file_path):
df = pd.read_excel(file_path)
report = []
for _, row in df.iterrows():
if row['操作类型'] == 'DELETE':
status, msg = delete_invoice(row['凭证号'], row['年度'])
elif row['操作类型'] == 'CANCEL':
status, msg = cancel_invoice(
row['凭证号'],
row['年度'],
row.get('冲销原因', '03'),
row.get('过账日期')
)
report.append({
'原始凭证': row['凭证号'],
'状态': '成功' if status else '失败',
'消息': msg,
'新凭证': msg.get('new_doc') if isinstance(msg, dict) else None
})
return pd.DataFrame(report)
3.2 结果报告与异常处理
生成带格式的Excel报告:
def generate_report(result_df, output_file):
writer = pd.ExcelWriter(output_file, engine='xlsxwriter')
result_df.to_excel(writer, index=False)
workbook = writer.book
worksheet = writer.sheets['Sheet1']
format_success = workbook.add_format({'bg_color': '#C6EFCE'})
format_failure = workbook.add_format({'bg_color': '#FFC7CE'})
for idx, status in enumerate(result_df['状态']):
if status == '成功':
worksheet.set_row(idx+1, None, format_success)
else:
worksheet.set_row(idx+1, None, format_failure)
writer.close()
4. 高级应用与性能优化
4.1 并行处理加速
使用
concurrent.futures
实现并行处理:
from concurrent.futures import ThreadPoolExecutor
def parallel_process(file_path, max_workers=5):
df = pd.read_excel(file_path)
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
for _, row in df.iterrows():
if row['操作类型'] == 'DELETE':
futures.append(executor.submit(
delete_invoice,
row['凭证号'],
row['年度']
))
# 其他操作类似...
results = [f.result() for f in futures]
return pd.DataFrame([{
'原始凭证': df.iloc[idx]['凭证号'],
'状态': '成功' if res[0] else '失败',
'消息': res[1]
} for idx, res in enumerate(results)])
4.2 自动重试机制
对暂时性错误实现智能重试:
from time import sleep
def robust_call(bapi_name, max_retries=3, **kwargs):
for attempt in range(max_retries):
try:
result = conn.call(bapi_name, **kwargs)
if any(msg['TYPE'] == 'E' for msg in result.get('RETURN', [])):
if attempt == max_retries - 1:
return False, result['RETURN']
sleep(2 ** attempt) # 指数退避
continue
return True, result
except (ConnectionError, RFCError) as e:
if attempt == max_retries - 1:
return False, str(e)
conn.reopen() # 重建连接
sleep(2 ** attempt)
5. 系统集成方案
5.1 与邮件系统联动
处理完成后自动发送结果邮件:
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
def send_report(email_to, report_file):
msg = MIMEMultipart()
msg['Subject'] = 'SAP发票处理报告'
msg['From'] = 'sap_auto@example.com'
msg['To'] = email_to
with open(report_file, 'rb') as f:
part = MIMEBase('application', 'octet-stream')
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', f'attachment; filename="{report_file}"')
msg.attach(part)
with smtplib.SMTP('smtp.example.com') as server:
server.send_message(msg)
5.2 定时任务配置
使用Windows任务计划或Linux cron实现定时运行:
# 每天上午6点运行
0 6 * * * /path/to/python /scripts/sap_invoice_auto.py
对于更复杂的调度,可以集成到Airflow等平台:
from datetime import datetime
from airflow import DAG
from airflow.operators.python_operator import PythonOperator
default_args = {
'start_date': datetime(2023, 1, 1),
}
dag = DAG('sap_invoice_processing',
schedule_interval='0 6 * * *',
default_args=default_args)
run_script = PythonOperator(
task_id='process_invoices',
python_callable=main_processing_function,
dag=dag
)
更多推荐


所有评论(0)