您的采购到付款发票处理数据模板
您的采购到付款发票处理数据模板
- 建议收集的属性
- 需要跟踪的关键活动
- SAP S/4HANA提取指南
采购到付款-发票处理属性
| 名称 | 说明 | ||
|---|---|---|---|
| 发票编号 InvoiceNumber | 供应商发票凭证的唯一标识符,也是流程的主案例标识。 | ||
| 说明 发票编号是SAP S/4HANA中为每张供应商发票分配的唯一标识符。它将创建、暂存、审批和付款等所有相关活动连接为一个完整的流程实例。 在流程挖掘中,该属性是跟踪每张发票端到端流转过程的基础。它支持重建从接收到最终付款的完整流程路径,从而在单张发票层面分析周期时间、瓶颈和流程变体。 为什么重要 这是连接所有相关事件的关键标识,可完整追踪发票在系统中的生命周期。 获取位置 这是会计凭证编号,位于BKPF表的BELNR字段。 示例 190000000119000000451900000132 | |||
| 事件时间 EventTime | 活动发生的准确日期和时间。 | ||
| 说明 事件时间是记录具体活动准确发生时刻的时间戳。该数据对于计算流程不同步骤之间的持续时间、周期时间和等待时间至关重要。 在流程挖掘分析中,准确的时间戳用于衡量“平均发票周期时间”和“发票审批周期时间”等绩效KPI。通过分析活动之间经过的时间,企业可以定位发票延迟的瓶颈,并发现加快流程的机会。 为什么重要 该时间戳是所有基于时间的分析的基础,包括绩效监控、瓶颈识别和SLA跟踪。 获取位置 通常来源于变更凭证表CDHDR(抬头)和CDPOS(项目),使用UDATE和UTIME字段。对于部分事件,也可能来源于BKPF等表中的创建日期或录入日期(CPUDT、CPUTM)。 示例 2023-04-15T10:30:00Z2023-04-18T14:05:21Z2023-05-02T09:00:00Z | |||
| 活动名称 ActivityName | 发票在特定时间点发生的业务活动或事件名称。 | ||
| 说明 活动名称描述发票处理生命周期中的具体步骤或状态变更。例如:“发票凭证已创建”“发票已发送审批”“已设置付款冻结”和“付款已执行”。 该属性对于构建流程图至关重要,流程图以可视化方式呈现活动流转。分析活动的顺序、频率及其间隔时长,有助于识别瓶颈、返工循环和不合规的流程变体。它是流程挖掘分析的基础。 为什么重要 它定义流程中的步骤,从而支持流程图可视化,以及对顺序流和变体的分析。 获取位置 由SAP事务代码(SY-TCODE)、变更凭证对象状态(CDHDR/CDPOS)以及表示状态变更的特定字段值组合得出。 示例 发票暂存发票审批通过付款已执行 | |||
| 付款冻结原因 PaymentBlockReason | 表示发票无法付款原因的代码。 | ||
| 说明 发票被冻结付款时,该属性提供具体冻结原因,例如“数量差异”或“价格不匹配”。这些原因在SAP中配置,用于标准化异常处理。 该属性对于“付款冻结发生次数与持续时间”仪表板至关重要。分析不同冻结原因的频率,有助于识别付款延迟的根因,例如特定供应商、物料或内部流程的问题,从而采取有针对性的纠正措施。 为什么重要 提供付款冻结的具体根因,支持有针对性的分析,以减少延迟并提升一次处理正确率。 获取位置 位于BSEG表供应商行项目的ZLSPR字段(付款冻结键)。 示例 RIA | |||
| 付款到期日 PaymentDueDate | 为避免逾期,必须完成发票付款的日期。 | ||
| 说明 付款到期日是根据发票日期和约定付款条件计算出的供应商付款截止日期,是流程中的关键期限。 该属性对于“按时付款率”KPI和“供应商付款绩效”仪表板至关重要。通过比较实际付款日期与到期日,企业可以衡量履行付款义务的能力,这会影响供应商关系和财务信誉。 为什么重要 它是衡量按时付款表现的主要基准,对于维护良好的供应商关系和避免滞纳金至关重要。 获取位置 该日期通常可直接从BSEG表供应商行项目的ZFBDT字段(到期日计算基准日期)获取。净到期日根据该基准日期和付款条件计算。 示例 2023-05-302023-06-152023-07-01 | |||
| 供应商编号 VendorNumber | 提交发票的供应商唯一标识符。 | ||
| 说明 供应商编号标识与发票相关的供应商或债权人,并将发票交易连接到供应商主数据。 该属性对于供应商维度分析至关重要,例如评估“供应商付款绩效”,或识别经常提交问题发票、导致异常或付款冻结的供应商。它有助于管理供应商关系并评估供应商可靠性。 为什么重要 支持按供应商分析流程绩效,帮助识别模式、管理关系并评估供应商相关问题。 获取位置 通常位于会计凭证项目表BSEG的LIFNR字段。 示例 100345700012V9832 | |||
| 公司代码 CompanyCode | 代表依法独立公司、并负责编制财务报表的组织单位。 | ||
| 说明 公司代码是SAP财务中的基础组织单位。每张发票都会分配到特定公司代码,由此确定负责该交易的法人实体。 在流程挖掘中,按公司代码筛选或比较,对于分析不同业务部门、法人实体或国家的流程绩效至关重要。它有助于识别效率、合规和自动化水平方面的地区差异,从而支持有针对性的改进措施。 为什么重要 它支持在组织内部按不同法人实体或地理位置细分并比较发票处理绩效。 获取位置 这是凭证抬头表BKPF中的标准字段BUKRS。 示例 1000US01DE01 | |||
| 凭证类型 DocumentType | 用于区分不同会计凭证类型的代码,例如供应商发票或贷项凭证。 | ||
| 说明 SAP使用凭证类型区分不同业务交易。例如,“KR”通常表示标准供应商发票,“KG”可能表示供应商贷项凭证。 按凭证类型分析,可以细分流程,了解不同交易类型的处理方式。例如,贷项凭证的流程可能与标准发票明显不同。这种细分能够提供更准确、更相关的流程洞察。 为什么重要 它有助于区分不同类型的财务交易,例如标准发票和贷项凭证,因为它们通常遵循不同的流程路径。 获取位置 位于凭证抬头表BKPF的BLART字段。 示例 KRREKG | |||
| 发票金额 AmountInCompanyCodeCurrency | 公司代码本位币中的发票总金额。 | ||
| 说明 该属性表示发票总价值,是了解发票处理运营财务影响和规模的重要指标。 分析发票金额有助于优先处理高价值发票、识别支出趋势,并将流程问题与财务价值关联起来。例如,可用于调查高价值发票是否更容易被冻结,或审批时间是否更长。 为什么重要 为流程提供财务背景,支持按金额分析,例如识别高价值发票是否采用不同的处理方式。 获取位置 该值通常由BSEG表相关项目的WRBTR字段(本位币金额)求和得出。 示例 1500.75125000.00850.20 | |||
| 用户名 UserName | 执行活动的人员或系统的SAP用户ID。 | ||
| 说明 该属性标识执行特定事务或创建凭证的用户。它可以是个人用户ID,也可以是自动批处理作业使用的系统ID。 按用户分析有助于了解工作量分配、识别培训需求并发现异常用户行为。例如,它可以显示哪些用户经常处理异常,或哪些发票由系统自动处理(例如用户“BATCHUSER”),这对于计算“发票自动化率”KPI至关重要。 为什么重要 它将流程活动归属到具体用户或系统账户,从而支持工作量分析、绩效比较和自动化识别。 获取位置 来源包括BKPF-USNAM(录入人)或CDHDR-USERNAME(变更人)等字段。 示例 SMITHJMUELLERTWF-BATCH | |||
| 采购订单 PurchasingDocument | 与发票相关的采购订单编号。 | ||
| 说明 采购凭证编号将供应商发票连接到原始采购订单(PO)。该连接是三方匹配流程的基础,用于将发票与采购订单和收货记录进行核对。 按此属性分析,有助于了解基于PO与非PO发票的相关问题。它是调查匹配差异和了解采购环节效率的关键。 为什么重要 将发票连接到采购流程,对于分析匹配差异和PO合规至关重要。 获取位置 该信息通常位于凭证项目表BSEG的EBELN字段(采购凭证编号)。 示例 450000123445000056784500009012 | |||
| 付款条件 PaymentTerms | 定义与供应商约定的付款条件的代码,例如到期日和折扣期限。 | ||
| 说明 付款条件定义发票付款规则,包括提前付款可享受的折扣。例如,“Z030”可能表示“净额30天内付款”。 该属性对于财务规划和营运资金优化至关重要。在流程挖掘中,它用于计算“付款到期日”和确定提前付款折扣资格,直接支持“早付款折扣获取率”KPI。 为什么重要 定义付款到期日和折扣规则,直接影响按时付款KPI和营运资金管理。 获取位置 位于BSEG表供应商行项目的ZTERM字段(付款条件键)。 示例 0001Z030NT60 | |||
| 冲销原因 ReversalReason | 表示发票凭证被冲销原因的代码。 | ||
| 说明 如果发票过账错误,通常会执行冲销。冲销原因代码说明执行该操作的原因,例如“过账日期错误”或“数据录入错误”。 分析冲销原因有助于识别发票过账流程中的错误模式。这些洞察可用于改进培训、加强系统控制,或解决导致财务返工和管理开销的重复性问题。 为什么重要 说明发票被取消的原因,直接揭示过账流程中的错误和返工来源。 获取位置 位于原始凭证抬头表BKPF的STGRD字段(冲销原因)。 示例 010205 | |||
| 发票日期 InvoiceDate | 供应商开具发票凭证的日期。 | ||
| 说明 发票日期也称凭证日期,是供应商在发票上提供的日期。系统根据约定付款条件,以此作为计算付款到期日的起点。 在分析中,该日期是计算发票账龄和早付款折扣资格等财务指标的基础,也是“早付款折扣获取率”KPI的重要输入。 为什么重要 作为计算付款条件和到期日的基准,对于营运资金管理和获取折扣至关重要。 获取位置 位于凭证抬头表BKPF的BLDAT字段(凭证日期)。 示例 2023-04-122023-05-152023-06-20 | |||
| 审批周期次数 ApprovalCycleCount | 发票提交审批的次数。 | ||
| 说明 该指标统计单张发票发生“发票已发送审批”活动的次数。次数大于1表示发票至少被拒绝或退回过一次,需要重新进行审批。 该属性直接支持“首次审批通过率”KPI。通过分析审批周期次数较高的发票,企业可以识别审批失败原因,例如信息不足或编码错误,并采取措施改进流程。 为什么重要 量化审批子流程中的返工,有助于衡量一次处理正确率并识别审批被拒的原因。 获取位置 通过统计每个唯一InvoiceNumber对应的“发票已发送审批”活动次数计算得出。 示例 123 | |||
| 提取时间戳 ExtractionTimestamp | 从源系统提取数据的日期和时间。 | ||
| 说明 该属性记录数据提取事件的时间戳,反映流程挖掘工具中待分析数据的新鲜度。 在分析中,它用于了解所生成洞察的时效性。对于运营监控仪表板而言,该属性至关重要,可确保决策基于最新信息,并有效管理数据刷新周期。 为什么重要 表示数据的新鲜度,确保分析和报告基于当前可用的最新信息。 获取位置 这不是SAP字段,而是在提取数据或ETL流程拉取数据时由数据提取工具生成并添加。 示例 2023-10-27T02:00:00Z2023-10-28T02:00:00Z2023-10-29T02:00:00Z | |||
| 是否按时付款 IsPaidOnTime | 如果发票在付款到期日当天或之前完成付款,则该标记为true。 | ||
| 说明 该布尔属性通过比较实际付款日期(“付款已执行”活动的时间戳)与“付款到期日”得出,为每张发票的付款状态提供明确的二元结果。 这是计算“按时付款率”KPI的核心。通过筛选和分析,企业可以了解逾期付款的特征,例如与延迟相关的常见供应商、公司代码或发票金额。 为什么重要 直接衡量付款是否遵守付款条件,是供应商关系管理和财务运营的关键KPI。 获取位置 通过比较“付款已执行”活动的EventTime与PaymentDueDate属性计算得出。(付款日期 <= PaymentDueDate)。 示例 truefalse | |||
| 是否自动执行 IsAutomated | 表示活动是否由自动化系统用户执行的标记。 | ||
| 说明 当活动关联用户是已知系统账户或批处理账户(例如“WF-BATCH”或“SAP_SYSTEM”)时,该布尔属性为true。它有助于区分手动和自动化流程步骤。 该属性对于计算“发票自动化率”KPI至关重要。通过分析流程中哪些环节已实现自动化,企业可以衡量自动化计划的成效,并发现进一步减少人工操作、提升效率的机会。 为什么重要 区分手动活动和系统驱动活动,是衡量自动化率和识别进一步自动化机会的基础。 获取位置 由UserName属性得出。通过映射或规则,将特定用户ID归类为“自动化用户”。 示例 truefalse | |||
| 是否返工 IsRework | 表示发票是否经历返工活动的标记,例如审批被拒或付款冻结被解除。 | ||
| 说明 该属性标记经历过一次或多次返工循环的发票。返工可通过特定活动序列识别,例如“发票已拒绝”后出现“发票已批准”,或“已设置付款冻结”后出现“已解除付款冻结”。 该属性简化了“发票返工率”KPI的计算。分析人员可以轻松筛选并调查发生返工的案例,了解效率低下和重复人工操作的根因。 为什么重要 识别必须重复执行工作的低效流程,帮助量化浪费并定位流程异常的根因。 获取位置 根据事件日志中的活动顺序计算。例如,如果发票轨迹中出现“发票已拒绝”,则该标记设为true。 示例 truefalse | |||
| 清账凭证编号 ClearingDocumentNumber | 用于清账发票的凭证编号,通常代表付款凭证。 | ||
| 说明 清账凭证编号将未清发票项目连接到清账交易,该交易几乎总是付款凭证。这确认发票已完成付款。 该属性是发票与付款之间的确定性连接,用于识别“付款已执行”活动及其对应时间戳,对于计算端到端周期时间和按时付款率至关重要。 为什么重要 确认发票已付款,并将其连接到具体付款交易,对于周期时间和付款绩效分析至关重要。 获取位置 位于凭证项目表BSEG的AUGBL字段(清账凭证编号)。 示例 150000000115000000231500000088 | |||
| 源系统ID SourceSystemId | 提取数据的源SAP S/4HANA系统标识符。 | ||
| 说明 该属性指定数据来源系统,例如“S4H_PROD”或“ERP_EU”。在包含多个ERP实例,或同时使用旧系统和现代系统的环境中,这一属性尤为重要。 在分析中,它支持比较不同系统或地区的流程绩效,确保数据来源可追溯;当多个来源的数据汇总到中央流程挖掘平台时,它对于数据治理和问题排查也至关重要。 为什么重要 它提供数据来源的上下文信息,对于数据治理,以及比较不同系统或公司地点的流程至关重要。 获取位置 该值通常在提取数据时从SAP系统ID(sy-sysid)得出,或在ETL管道中配置为静态值。 示例 S4PS4H_PROD_100ECC_EU | |||
采购到付款-发票处理活动
| 活动 | 说明 | ||
|---|---|---|---|
| 付款已执行 | 这是标准流程中的最后一个活动,付款完成且发票被清账,表示资金已支付给供应商。 | ||
| 为什么重要 这标志着P2P发票生命周期的结束。它对于计算端到端总周期时间,以及根据到期日衡量按时付款表现至关重要。 获取位置 从供应商行项目的清账凭证信息中捕获此事件。清账日期(BSEG-AUGDT)和清账凭证(BSEG-AUGBL)表明付款已完成。 采集 使用已清账供应商行项目中的清账日期(BSEG-AUGDT)。 事件类型 explicit | |||
| 创建发票单据 | 这是第一个事件,标志着SAP中发票单据的创建。当用户保存新发票单据时即可捕获该事件,此时单据可能处于暂存或预过账状态。 | ||
| 为什么重要 该活动标志着发票处理生命周期的开始。分析从该事件到其他事件的时间,对于衡量整体处理周期至关重要。 获取位置 该事件从单据抬头表中的创建日期和时间(CPUDT、CPUTM)捕获,通常来自BKPF;对于物流发票,则通常来自RBKP。FB60、MIRO或MIR7等事务代码(BKPF-TCODE)可表示创建方式。 采集 使用BKPF-CPUDT和BKPF-CPUTM中的创建时间戳记录发票单据。 事件类型 explicit | |||
| 发票审批通过 | 该活动表示发票已获指定审批人员批准。当审批工作流成功结束或设置释放标识时,即可捕获该事件。 | ||
| 为什么重要 这是解除发票付款限制的关键里程碑。审批延迟是常见瓶颈,跟踪该活动有助于定位审批缓慢的人员或流程步骤。 获取位置 可以根据SAP工作流中的最终释放步骤推断,也可以跟踪与发票或其采购单据相关表中的释放状态字段变化。 采集 根据工作流完成事件或单据释放状态字段的变化进行推断。 事件类型 inferred | |||
| 发票已冲销 | 表示冲销此前已过账发票凭证的活动。对于错误发票,这是一个终止事件,之后通常会重新正确录入发票。 | ||
| 为什么重要 冲销表明流程早期未发现关键错误。跟踪冲销频率及其根因,对于改进流程和减少财务错误至关重要。 获取位置 创建冲销凭证时即可识别冲销。原始凭证抬头(BKPF)会包含冲销凭证编号(BKPF-STBLG),反之亦然。冲销凭证的过账日期即为事件时间。 采集 识别BKPF-STBLG字段有值的凭证,并使用冲销凭证的过账日期。 事件类型 explicit | |||
| 发票已过账 | 这是一个关键财务事件:系统会将暂存或已批准的发票正式过账到总账。此操作确认对供应商的负债。 | ||
| 为什么重要 过账是一个重要里程碑,将数据录入和审批与财务结算阶段分开。从发票创建到过账的时间,是衡量内部处理效率的重要指标。 获取位置 通过凭证抬头中的过账日期(BKPF-BUDAT)识别此事件。对于先暂存的凭证,状态转为已过账时的时间即为事件时间戳。 采集 使用过账日期(BKPF-BUDAT)作为事件时间戳。 事件类型 explicit | |||
| 付款建议已创建 | 发票在付款运行中被选中并纳入付款建议。这是自动付款流程的第一步。 | ||
| 为什么重要 此活动表示付款意图。该步骤与最终执行付款之间的延迟,可能暴露付款运行、审批或银行通信方面的问题。 获取位置 可在付款运行表中找到,具体为REGUP表,其中包含付款建议中的项目。对应REGUH表中的运行日期提供时间戳。 采集 识别发票在付款建议运行中出现在REGUP表的时间。 事件类型 explicit | |||
| 发票提交审批 | 该活动标志着发票正式审批工作流的启动。通常可根据发票状态变为“待审批”,或生成工作流项目来推断。 | ||
| 为什么重要 这是衡量审批周期时间的起点。了解审批何时开始,对于识别审批工作流本身的瓶颈至关重要。 获取位置 通常可根据与发票对象(例如BUS2081)关联的SAP Business Workflow启动记录(SWW_WI2OBJ表),或单据抬头中的自定义状态字段变化来推断。 采集 根据与发票单据相关的工作流项目创建记录进行推断。 事件类型 inferred | |||
| 发票数据已更新 | 该活动反映发票单据在初次创建后发生的变更。这种情况常见于发票被拒绝后的返工周期,或用于纠正错误。 | ||
| 为什么重要 频繁更新通常表明存在返工,以及录入环节潜在的数据质量问题。跟踪这些变更有助于量化纠错投入,并识别常见错误。 获取位置 关键字段的变更会记录在SAP的变更单据表中:CDHDR(抬头)和CDPOS(项目)。通过筛选相关发票对象的变更记录,即可生成事件。 采集 从CDHDR和CDPOS表中提取发票对象的变更事件。 事件类型 explicit | |||
| 发票暂存 | 表示发票已录入系统,但尚未过账至总账。暂存用于保存不完整发票,或在过账前留待后续审核。 | ||
| 为什么重要 暂存表示流程被有意暂停。跟踪暂存发票的持续时间和发生频率,有助于识别正式过账和审批周期开始前的延误原因。 获取位置 可以通过暂存事务(例如MIR7、FV60)创建的单据识别,也可以检查BKPF表中的特定状态字段或VBKPF等专用暂存单据表。 采集 识别通过暂存事务创建的单据,或检查暂存单据状态。 事件类型 explicit | |||
| 发票被拒绝 | 表示发票在审批过程中被拒绝。该事件会触发返工,需要更正后重新提交。 | ||
| 为什么重要 发票被拒绝是流程低效和数据质量问题的重要指标。分析拒绝频率和原因,有助于发现改进和培训机会。 获取位置 可以根据SAP工作流中的特定状态更新(例如“已拒绝”),或根据取消当前审批工作流并将发票退回处理人员的事件进行推断。 采集 根据表示拒绝的工作流状态变化进行推断。 事件类型 inferred | |||
| 解除付款冻结 | 表示问题已解决,之前设置的付款冻结被移除,发票重新具备付款资格。 | ||
| 为什么重要 设置付款冻结到解除冻结之间的时间,代表流程异常的解决时间。缩短这一时长是提升效率和改善供应商关系的关键。 获取位置 清除付款冻结键字段(BSEG-ZLSPR)时记录此事件。该变更会记录在CDHDR和CDPOS表中,并提供解除冻结的时间戳。 采集 通过变更凭证(CDHDR/CDPOS)识别BSEG-ZLSPR字段被清除的时间。 事件类型 explicit | |||
| 设置付款冻结 | 在发票上有意设置冻结,以阻止付款的活动。常见原因包括价格或数量存在差异,或待处理的贷项通知单。 | ||
| 为什么重要 付款冻结是导致付款延迟和供应商争议的主要原因。分析冻结的频率、持续时间和原因,对于提高按时付款率至关重要。 获取位置 通过跟踪发票行项目中的付款冻结键字段(BSEG-ZLSPR)变更来捕获该事件。CDHDR和CDPOS中的变更日志会提供设置冻结的时间戳和操作人员。 采集 通过变更单据(CDHDR/CDPOS)识别BSEG-ZLSPR字段被填充的时间。 事件类型 explicit | |||
| 逾期付款已执行 | 这是一个计算事件,在发票付款晚于计算出的到期日时发生。该事件通过比较两个日期字段得出。 | ||
| 为什么重要 此活动直接支持按时付款KPI,并帮助识别经常逾期付款的供应商或业务部门。逾期付款可能损害供应商关系并导致罚款。 获取位置 通过比较清账日期(BSEG-AUGDT)与净到期日计算得出。到期日则根据基准日期(BSEG-ZFBDT)和付款条件(BSEG-ZTERM)计算。 采集 通过比较BSEG-AUGDT >(BSEG-ZFBDT + 付款条件天数)得出。 事件类型 calculated | |||
提取指南
步骤
- 前提条件和授权:确保执行提取的用户在SAP S/4HANA中拥有访问所需Core Data Services(CDS)视图的必要授权。关键视图包括
I_InvoiceDocument、I_OperationalAcctgDocItem、I_ChangeDocument、I_ChangeDocumentItem和I_PaymentProposalItem。用户还需要通过所选接口执行查询的权限,例如OData服务或直接SQL连接。 - 确定连接方式:确定连接SAP S/4HANA系统并执行SQL查询的方式。常见方式包括使用SAP Data Services、SAP Data Intelligence、带SAP连接器的第三方ETL工具,或在组织安全策略允许的情况下直接连接SAP HANA数据库。
- 定义提取参数:执行查询前定义关键参数。指定提取日期范围,例如
CreationDate介于'YYYY-MM-DD'和'YYYY-MM-DD'之间。同时确定需要纳入的CompanyCode值,以限制数据提取范围。 - 自定义SQL查询:将提供的SQL查询复制到所选SQL客户端或数据提取工具中。仔细检查
'{StartDate}'、'{EndDate}'和('{CompanyCode1}', '{CompanyCode2}')等占位符,并替换为上一步定义的实际值。您可能还需要根据具体SAP配置调整工作流状态字段名称。 - 执行查询:针对SAP S/4HANA数据库运行完整SQL查询,或通过适当的服务层执行。该查询内容全面,具体运行时间取决于数据量和所选日期范围,可能需要较长时间。请监控执行过程,及时发现潜在错误或超时。
- 检查初始结果:查询完成后快速检查输出。确认
InvoiceNumber、ActivityName和EventTime列均已填充,并验证ActivityName列中包含多种不同活动,而不只是“Invoice Document Created”。 - 处理数据转换:该查询旨在生成规范的事件日志格式。但请确保
EventTime列采用一致的时间戳格式,例如YYYY-MM-DDTHH:MM:SS。在必要时,提供的查询会将日期和时间字段合并为单一时间戳。 - 导出数据:将工具中的最终结果集导出为CSV(逗号分隔值)文件。该格式与包括ProcessMind在内的流程挖掘工具普遍兼容。
- 准备上传:上传前确认CSV文件使用UTF-8编码,以避免字符显示问题。确保文件列标题与所需属性完全一致:
InvoiceNumber、ActivityName、EventTime、UserName、CompanyCode等。 - 上传到ProcessMind:将准备好的CSV文件上传到流程挖掘项目,并在工具的数据模型配置中,将文件列映射到对应的案例ID、活动名称和时间戳字段。
配置
- 使用的CDS视图:主要数据源为标准SAP CDS视图。关键视图包括:用于抬头数据的
I_InvoiceDocument,用于财务过账和清账明细的I_OperationalAcctgDocItem,以及用于跟踪发票属性历史变更的I_ChangeDocument和I_ChangeDocumentItem,例如付款冻结和工作流状态。 - 日期范围筛选:必须按具体日期范围筛选数据,以控制性能。提供的查询使用占位符筛选
I_InvoiceDocument视图中的CreationDate。建议先提取3至6个月的数据。 - 公司代码筛选:为确保提取结果相关且规模可控,请始终按一个或多个
CompanyCode进行筛选。查询包含占位条件WHERE inv.CompanyCode IN ('{CompanyCode1}', '{CompanyCode2}')。 - 凭证类型筛选:您还可以按
InvoiceDocumentType进一步细化提取范围。例如,可纳入标准供应商发票(RE),排除贷项通知单。此条件可添加到初始CTE的WHERE子句中。 - 前提条件:执行查询的用户需要在指定公司代码范围内拥有财务和采购凭证的显示授权。通过SQL客户端访问底层HANA数据库并非标准权限,需要特别授权。
- 性能注意事项:从变更凭证表(
I_ChangeDocument、I_ChangeDocumentItem)提取数据可能消耗较多性能。必须严格按日期、公司代码和对象类别(INCOMINGINVOICE)筛选,以避免执行时间过长。
a 示例查询 sql
WITH InvoiceBase AS (
SELECT
inv.InvoiceDocument,
inv.FiscalYear,
inv.CompanyCode,
inv.Supplier AS VendorNumber,
inv.DocumentType,
inv.GrossInvoiceAmountInCoCoCrcy AS AmountInCompanyCodeCurrency,
inv.NetDueDate AS PaymentDueDate,
inv.PurchasingDocument,
inv.CreationDateTime,
inv.CreatedByUser,
accdoc.AccountingDocument,
accdoc.ClearingDate,
accdoc.ClearingJournalEntry,
accdoc.PaymentBlockReason,
accdoc.IsReversed
FROM I_InvoiceDocument AS inv
LEFT JOIN I_OperationalAcctgDocItem AS accdoc
ON inv.AccountingDocument = accdoc.AccountingDocument
AND inv.FiscalYear = accdoc.FiscalYear
AND inv.CompanyCode = accdoc.CompanyCode
WHERE
inv.CreationDate BETWEEN '{StartDate}' AND '{EndDate}'
AND inv.CompanyCode IN ('{CompanyCode1}', '{CompanyCode2}')
)
-- 1. Invoice Document Created
SELECT
InvoiceDocument AS "InvoiceNumber",
'Invoice Document Created' AS "ActivityName",
CreationDateTime AS "EventTime",
CreatedByUser AS "UserName",
CompanyCode AS "CompanyCode",
VendorNumber AS "VendorNumber",
AmountInCompanyCodeCurrency AS "AmountInCompanyCodeCurrency",
PaymentDueDate AS "PaymentDueDate",
DocumentType AS "DocumentType",
CAST(NULL AS VARCHAR(1)) AS "PaymentBlockReason",
PurchasingDocument AS "PurchasingDocument"
FROM InvoiceBase
UNION ALL
-- 2. Invoice Parked
SELECT
i.InvoiceDocument AS "InvoiceNumber",
'Invoice Parked' AS "ActivityName",
i.CreationDateTime AS "EventTime",
i.CreatedByUser AS "UserName",
i.CompanyCode AS "CompanyCode",
i.Supplier AS "VendorNumber",
i.GrossInvoiceAmountInCoCoCrcy AS "AmountInCompanyCodeCurrency",
i.NetDueDate AS "PaymentDueDate",
i.DocumentType AS "DocumentType",
CAST(NULL AS VARCHAR(1)) AS "PaymentBlockReason",
i.PurchasingDocument AS "PurchasingDocument"
FROM I_InvoiceDocument AS i
WHERE
i.InvoiceDocumentIsParked = 'X'
AND i.CreationDate BETWEEN '{StartDate}' AND '{EndDate}'
AND i.CompanyCode IN ('{CompanyCode1}', '{CompanyCode2}')
UNION ALL
-- 3, 4, 5. Workflow activities (Sent for Approval, Approved, Rejected) from Change Docs
SELECT
cdpos.ObjectValue AS "InvoiceNumber",
CASE
WHEN cdpos.ValueNew = '[StatusSentForApproval]' THEN 'Invoice Sent For Approval'
WHEN cdpos.ValueNew = '[StatusApproved]' THEN 'Invoice Approved'
WHEN cdpos.ValueNew = '[StatusRejected]' THEN 'Invoice Rejected'
END AS "ActivityName",
CAST(cdhdr.ChangeDate AS TIMESTAMP) + CAST(cdhdr.ChangeTime AS TIME) AS "EventTime",
cdhdr.UserName AS "UserName",
inv.CompanyCode AS "CompanyCode",
inv.VendorNumber AS "VendorNumber",
inv.AmountInCompanyCodeCurrency AS "AmountInCompanyCodeCurrency",
inv.PaymentDueDate AS "PaymentDueDate",
inv.DocumentType AS "DocumentType",
CAST(NULL AS VARCHAR(1)) AS "PaymentBlockReason",
inv.PurchasingDocument AS "PurchasingDocument"
FROM I_ChangeDocument AS cdhdr
JOIN I_ChangeDocumentItem AS cdpos ON cdhdr.ChangeDocument = cdpos.ChangeDocument
JOIN InvoiceBase AS inv ON cdpos.ObjectValue = inv.InvoiceDocument
WHERE
cdhdr.ObjectClassName = 'INCOMINGINVOICE'
AND cdpos.FieldName = '[WorkflowStatusFieldName]'
AND cdpos.ValueNew IN ('[StatusSentForApproval]', '[StatusApproved]', '[StatusRejected]')
UNION ALL
-- 6. Invoice Data Updated
SELECT
cdpos.ObjectValue AS "InvoiceNumber",
'Invoice Data Updated' AS "ActivityName",
CAST(cdhdr.ChangeDate AS TIMESTAMP) + CAST(cdhdr.ChangeTime AS TIME) AS "EventTime",
cdhdr.UserName AS "UserName",
inv.CompanyCode AS "CompanyCode",
inv.VendorNumber AS "VendorNumber",
inv.AmountInCompanyCodeCurrency AS "AmountInCompanyCodeCurrency",
inv.PaymentDueDate AS "PaymentDueDate",
inv.DocumentType AS "DocumentType",
CAST(NULL AS VARCHAR(1)) AS "PaymentBlockReason",
inv.PurchasingDocument AS "PurchasingDocument"
FROM I_ChangeDocument AS cdhdr
JOIN I_ChangeDocumentItem AS cdpos ON cdhdr.ChangeDocument = cdpos.ChangeDocument
JOIN InvoiceBase AS inv ON cdpos.ObjectValue = inv.InvoiceDocument
WHERE
cdhdr.ObjectClassName = 'INCOMINGINVOICE'
AND cdpos.FieldName IN ('GrossInvoiceAmount', 'DocumentDate', 'PaymentTerms')
AND cdhdr.ChangeDate BETWEEN '{StartDate}' AND '{EndDate}'
UNION ALL
-- 7 & 8. Payment Block Set/Removed
SELECT
inv.InvoiceDocument AS "InvoiceNumber",
CASE
WHEN cdpos.ValueNew <> '' AND cdpos.ValueOld = '' THEN 'Payment Block Set'
WHEN cdpos.ValueNew = '' AND cdpos.ValueOld <> '' THEN 'Payment Block Removed'
END AS "ActivityName",
CAST(cdhdr.ChangeDate AS TIMESTAMP) + CAST(cdhdr.ChangeTime AS TIME) AS "EventTime",
cdhdr.UserName AS "UserName",
inv.CompanyCode AS "CompanyCode",
inv.VendorNumber AS "VendorNumber",
inv.AmountInCompanyCodeCurrency AS "AmountInCompanyCodeCurrency",
inv.PaymentDueDate AS "PaymentDueDate",
inv.DocumentType AS "DocumentType",
cdpos.ValueNew AS "PaymentBlockReason",
inv.PurchasingDocument AS "PurchasingDocument"
FROM I_ChangeDocument AS cdhdr
JOIN I_ChangeDocumentItem AS cdpos ON cdhdr.ChangeDocument = cdpos.ChangeDocument
JOIN InvoiceBase AS inv ON cdpos.ObjectValue = inv.AccountingDocument
WHERE
cdhdr.ObjectClassName = 'BELEG'
AND cdpos.TableName = 'BSEG'
AND cdpos.FieldName = 'ZLSPR'
AND ( (cdpos.ValueNew <> '' AND cdpos.ValueOld = '') OR (cdpos.ValueNew = '' AND cdpos.ValueOld <> '') )
UNION ALL
-- 9. Invoice Posted
SELECT
inv.InvoiceDocument AS "InvoiceNumber",
'Invoice Posted' AS "ActivityName",
CAST(accdoc.PostingDate AS TIMESTAMP) AS "EventTime",
accdoc.CreatedByUser AS "UserName",
inv.CompanyCode AS "CompanyCode",
inv.VendorNumber AS "VendorNumber",
inv.AmountInCompanyCodeCurrency AS "AmountInCompanyCodeCurrency",
inv.PaymentDueDate AS "PaymentDueDate",
inv.DocumentType AS "DocumentType",
accdoc.PaymentBlockReason AS "PaymentBlockReason",
inv.PurchasingDocument AS "PurchasingDocument"
FROM InvoiceBase AS inv
JOIN I_OperationalAcctgDocItem AS accdoc ON inv.AccountingDocument = accdoc.AccountingDocument
WHERE inv.AccountingDocument IS NOT NULL AND inv.IsReversed = FALSE
UNION ALL
-- 10. Payment Proposal Created
SELECT
item.InvoiceReference AS "InvoiceNumber",
'Payment Proposal Created' AS "ActivityName",
CAST(prun.PaymentRunDate AS TIMESTAMP) AS "EventTime",
prun.CreatedByUser AS "UserName",
item.CompanyCode AS "CompanyCode",
item.Supplier AS "VendorNumber",
item.AmountInTransactionCurrency AS "AmountInCompanyCodeCurrency",
item.NetDueDate AS "PaymentDueDate",
item.AccountingDocumentType AS "DocumentType",
item.PaymentBlockReason AS "PaymentBlockReason",
item.PurchasingDocument AS "PurchasingDocument"
FROM I_PaymentProposalItem as item
JOIN I_PaymentRun as prun ON item.PaymentRunName = prun.PaymentRunName
JOIN InvoiceBase AS inv ON item.InvoiceReference = inv.InvoiceDocument
UNION ALL
-- 11 & 12. Payment Executed / Late Payment Executed
SELECT
InvoiceDocument AS "InvoiceNumber",
CASE
WHEN ClearingDate > PaymentDueDate THEN 'Late Payment Executed'
ELSE 'Payment Executed'
END AS "ActivityName",
CAST(ClearingDate AS TIMESTAMP) AS "EventTime",
CAST(NULL AS VARCHAR(12)) AS "UserName", -- User for clearing is not always straightforward
CompanyCode AS "CompanyCode",
VendorNumber AS "VendorNumber",
AmountInCompanyCodeCurrency AS "AmountInCompanyCodeCurrency",
PaymentDueDate AS "PaymentDueDate",
DocumentType AS "DocumentType",
'' AS "PaymentBlockReason",
PurchasingDocument AS "PurchasingDocument"
FROM InvoiceBase
WHERE ClearingDate IS NOT NULL AND IsReversed = FALSE
UNION ALL
-- 13. Invoice Reversed
SELECT
rev.OriginalInvoiceDocument AS "InvoiceNumber",
'Invoice Reversed' AS "ActivityName",
rev.CreationDateTime AS "EventTime",
rev.CreatedByUser AS "UserName",
rev.CompanyCode AS "CompanyCode",
rev.Supplier AS "VendorNumber",
rev.GrossInvoiceAmountInCoCoCrcy AS "AmountInCompanyCodeCurrency",
CAST(NULL AS DATE) AS "PaymentDueDate",
rev.DocumentType AS "DocumentType",
CAST(NULL AS VARCHAR(1)) AS "PaymentBlockReason",
rev.PurchasingDocument AS "PurchasingDocument"
FROM I_InvoiceDocument AS rev
WHERE rev.OriginalInvoiceDocument IN (SELECT InvoiceDocument FROM InvoiceBase) AND rev.IsReversal = 'X' 步骤
- 确认已批准直接读取SAP HANA租户或数据库架构的权限,并获取只读数据库用户,使其拥有访问所需SAP表以及获批变更历史表或工作流表的授权。请使用SAP HANA Database Explorer、SAP HANA Studio或获批的SQL客户端。不要使用生产环境写入权限。
- 在目标S/4HANA系统中确认实际表名和列名。ACDOCA、BKPF和RBKP是标准起点,但工作流、暂存、付款建议、付款执行、变更历史和付款冻结历史可能使用特定版本或客户专用对象。请将查询中的每个方括号占位符替换为系统目录中已验证的对象,并确保符合相关业务含义。
- 使用[开始时间戳]和[结束时间戳]定义提取期间。提取足够宽的凭证和事件时间范围,通常为3至6个月;如果付款到期日或逾期付款可能晚于发票创建期间,则应延长范围。
- 使用已配置的发票键识别发票案例。查询使用InvoiceNumber作为案例标识,并在必要时在内部结合公司代码和会计年度,以避免编号冲突。请确认业务定义是否要求在ProcessMind案例标识中增加会计年度或公司代码键。
- 从RBKP、BKPF和ACDOCA映射已过账发票数据。使用RBKP获取发票抬头信息,使用BKPF获取会计凭证时间戳和用户,并在可用时使用ACDOCA获取供应商、金额、采购凭证、清账及付款相关会计信息。不要假设每种过账场景都会填充所有字段。
- 从已验证的系统专用数据源映射暂存、审批、拒绝、更新、付款冻结、付款建议和付款执行事件。将占位数据源替换为获批的视图或表,以提供事件时间戳、发票引用、用户、状态以及旧值和新值。每项活动都必须作为明确的事件行输出,因为ProcessMind不会自动推断事件。
- 在非生产或只读会话中执行完整SQL查询。查看执行计划,并在适当情况下按公司代码、凭证类型、会计年度和事件时间戳限制查询。避免对大型日记账表和历史表执行不受限制的连接。
- 使用下述检查验证结果。确认所有必需列均已存在,所有事件的EventTime均已填充,所有案例的InvoiceNumber均已填充,并在存在对应源数据时确认全部13个活动名称均出现。
- 将结果导出为ProcessMind支持的分隔文件,优先使用UTF-8编码的CSV。每行代表一个事件,列包括InvoiceNumber、ActivityName、EventTime、UserName、CompanyCode、VendorNumber、AmountInCompanyCodeCurrency、PaymentDueDate、DocumentType、PaymentBlockReason和PurchasingDocument。统一保留时间戳时区,并保留标识符前导零。
- 将事件日志上传到ProcessMind,并将InvoiceNumber配置为案例标识列,ActivityName配置为活动列,EventTime配置为时间戳列。如果ProcessMind配置支持额外案例键,请采用与提取阶段一致的复合键策略。
配置
- 日期范围:初次提取使用滚动3至6个月的时间范围。如果审批、付款、清账、冲销或逾期付款事件可能发生在发票创建期间之后,请纳入更早或更晚的历史数据。
- 公司范围:按[公司代码筛选条件]进行筛选,并确认所选公司代码已获准用于提取。
- 凭证范围:按[凭证类型筛选条件]进行筛选,纳入目标流程中代表供应商发票、贷项通知单、暂存发票和冲销的凭证类型。
- 案例标识:按照流程定义使用InvoiceNumber。如果发票编号并非全局唯一,请在源结果中保留公司代码和会计年度,或根据ProcessMind模型配置复合案例键。
- 事件来源:确认工作流状态变更、审批决定、暂存、变更历史、付款冻结、付款建议和付款执行的实际数据源。这些来源取决于S/4HANA版本、已启用范围、工作流设计和客户扩展。
- 时间戳策略:选择业务事件时间戳,而不是提取时间戳。记录时区,并在上传前统一转换所有事件时间戳。
- 金额策略:使用公司代码货币金额,并确认所选数据源是否一致存储借贷方向。除非已验证发票案例的汇总规则,否则不要汇总日记账行。
- 付款策略:明确“付款执行”代表清账、付款凭证过账、银行执行还是其他业务里程碑。使用与既定流程定义一致的数据源。
- 逾期付款:仅当Payment Executed发生在PaymentDueDate之后时,才输出Late Payment Executed。查询会明确计算此事件,不依赖ProcessMind推断。
- 性能:按日期、公司代码、凭证类型和相关会计年度限制源数据读取。仅选择所需列,检查查询计划;如果源历史数据量较大,可将获批的中间视图实体化。
- 前提条件:具备直接SAP HANA连接、所选对象的读取授权、元数据查看权限,以及访问应付账款、总账、采购、工作流和付款相关数据的权限。确认所需SAP许可证、数据库访问政策和数据保护审批均已落实。
- 安全性:使用只读技术用户保护供应商和付款数据,并遵循组织的传输、审计、脱敏和凭据管理政策。
a 示例查询 sql
WITH
invoice_base AS (
SELECT
r.INV_DOC_NO AS InvoiceNumber,
r.COMPANY_CODE AS CompanyCode,
r.FISCAL_YEAR AS FiscalYear,
r.DOCUMENT_TYPE AS DocumentType,
r.VENDOR_NO AS VendorNumber,
r.GROSS_AMOUNT_CC AS AmountInCompanyCodeCurrency,
r.PAYMENT_DUE_DATE AS PaymentDueDate,
r.PURCHASING_DOCUMENT AS PurchasingDocument,
r.CREATED_AT AS InvoiceCreatedAt,
r.CREATED_BY AS InvoiceCreatedBy
FROM [Your RBKP invoice header source] r
WHERE r.CREATED_AT >= '[Start timestamp]'
AND r.CREATED_AT < '[End timestamp]'
AND r.COMPANY_CODE IN ([Company code filter])
AND r.DOCUMENT_TYPE IN ([Document type filter])
),
posted_accounting AS (
SELECT
b.INV_DOC_NO AS InvoiceNumber,
b.COMPANY_CODE AS CompanyCode,
b.FISCAL_YEAR AS FiscalYear,
b.ACCOUNTING_DOCUMENT AS AccountingDocument,
b.POSTING_DATE AS PostingDate,
b.CREATED_AT AS PostedAt,
b.CREATED_BY AS PostedBy,
b.REVERSAL_DOCUMENT AS ReversalDocument,
b.REVERSED_DOCUMENT AS ReversedDocument
FROM [Your BKPF accounting document source] b
WHERE b.CREATED_AT >= '[Start timestamp]'
AND b.CREATED_AT < '[End timestamp]'
AND b.COMPANY_CODE IN ([Company code filter])
),
journal_attributes AS (
SELECT
a.COMPANY_CODE AS CompanyCode,
a.FISCAL_YEAR AS FiscalYear,
a.ACCOUNTING_DOCUMENT AS AccountingDocument,
MAX(a.VENDOR_NO) AS VendorNumber,
SUM(a.AMOUNT_IN_COMPANY_CODE_CURRENCY) AS AmountInCompanyCodeCurrency,
MAX(a.PURCHASING_DOCUMENT) AS PurchasingDocument,
MAX(a.CLEARING_DATE) AS ClearingDate,
MAX(a.CLEARING_DOCUMENT) AS ClearingDocument
FROM [Your ACDOCA universal journal source] a
WHERE a.COMPANY_CODE IN ([Company code filter])
AND a.POSTING_DATE >= '[Start date]'
AND a.POSTING_DATE < '[End date]'
GROUP BY
a.COMPANY_CODE,
a.FISCAL_YEAR,
a.ACCOUNTING_DOCUMENT
),
source_events AS (
SELECT
e.INV_DOC_NO AS InvoiceNumber,
e.COMPANY_CODE AS CompanyCode,
e.FISCAL_YEAR AS FiscalYear,
e.EVENT_TIMESTAMP AS EventTime,
e.EVENT_USER AS UserName,
e.PAYMENT_BLOCK_REASON AS PaymentBlockReason,
e.PURCHASING_DOCUMENT AS PurchasingDocument,
e.VENDOR_NO AS VendorNumber,
e.AMOUNT_IN_COMPANY_CODE_CURRENCY AS AmountInCompanyCodeCurrency,
e.PAYMENT_DUE_DATE AS PaymentDueDate,
e.DOCUMENT_TYPE AS DocumentType,
e.EVENT_TYPE AS SourceEventType
FROM [Your verified invoice event and workflow source] e
WHERE e.EVENT_TIMESTAMP >= '[Start timestamp]'
AND e.EVENT_TIMESTAMP < '[End timestamp]'
AND e.COMPANY_CODE IN ([Company code filter])
),
base_events AS (
SELECT
i.InvoiceNumber,
'Invoice Document Created' AS ActivityName,
i.InvoiceCreatedAt AS EventTime,
i.InvoiceCreatedBy AS UserName,
i.CompanyCode,
COALESCE(i.VendorNumber, j.VendorNumber) AS VendorNumber,
COALESCE(i.AmountInCompanyCodeCurrency, j.AmountInCompanyCodeCurrency) AS AmountInCompanyCodeCurrency,
i.PaymentDueDate,
i.DocumentType,
CAST(NULL AS NVARCHAR(20)) AS PaymentBlockReason,
COALESCE(i.PurchasingDocument, j.PurchasingDocument) AS PurchasingDocument
FROM invoice_base i
LEFT JOIN posted_accounting p
ON p.InvoiceNumber = i.InvoiceNumber
AND p.CompanyCode = i.CompanyCode
AND p.FiscalYear = i.FiscalYear
LEFT JOIN journal_attributes j
ON j.CompanyCode = p.CompanyCode
AND j.FiscalYear = p.FiscalYear
AND j.AccountingDocument = p.AccountingDocument
WHERE i.InvoiceCreatedAt IS NOT NULL
UNION ALL
SELECT
s.InvoiceNumber,
'Invoice Parked' AS ActivityName,
s.EventTime,
s.UserName,
s.CompanyCode,
COALESCE(s.VendorNumber, i.VendorNumber) AS VendorNumber,
COALESCE(s.AmountInCompanyCodeCurrency, i.AmountInCompanyCodeCurrency) AS AmountInCompanyCodeCurrency,
COALESCE(s.PaymentDueDate, i.PaymentDueDate) AS PaymentDueDate,
COALESCE(s.DocumentType, i.DocumentType) AS DocumentType,
s.PaymentBlockReason,
COALESCE(s.PurchasingDocument, i.PurchasingDocument) AS PurchasingDocument
FROM source_events s
LEFT JOIN invoice_base i ON i.InvoiceNumber = s.InvoiceNumber AND i.CompanyCode = s.CompanyCode AND i.FiscalYear = s.FiscalYear
WHERE s.SourceEventType = 'PARKED'
UNION ALL
SELECT s.InvoiceNumber, 'Invoice Sent For Approval', s.EventTime, s.UserName, s.CompanyCode, COALESCE(s.VendorNumber, i.VendorNumber), COALESCE(s.AmountInCompanyCodeCurrency, i.AmountInCompanyCodeCurrency), COALESCE(s.PaymentDueDate, i.PaymentDueDate), COALESCE(s.DocumentType, i.DocumentType), s.PaymentBlockReason, COALESCE(s.PurchasingDocument, i.PurchasingDocument)
FROM source_events s LEFT JOIN invoice_base i ON i.InvoiceNumber = s.InvoiceNumber AND i.CompanyCode = s.CompanyCode AND i.FiscalYear = s.FiscalYear
WHERE s.SourceEventType = 'SENT_FOR_APPROVAL'
UNION ALL
SELECT s.InvoiceNumber, 'Invoice Approved', s.EventTime, s.UserName, s.CompanyCode, COALESCE(s.VendorNumber, i.VendorNumber), COALESCE(s.AmountInCompanyCodeCurrency, i.AmountInCompanyCodeCurrency), COALESCE(s.PaymentDueDate, i.PaymentDueDate), COALESCE(s.DocumentType, i.DocumentType), s.PaymentBlockReason, COALESCE(s.PurchasingDocument, i.PurchasingDocument)
FROM source_events s LEFT JOIN invoice_base i ON i.InvoiceNumber = s.InvoiceNumber AND i.CompanyCode = s.CompanyCode AND i.FiscalYear = s.FiscalYear
WHERE s.SourceEventType = 'APPROVED'
UNION ALL
SELECT s.InvoiceNumber, 'Invoice Rejected', s.EventTime, s.UserName, s.CompanyCode, COALESCE(s.VendorNumber, i.VendorNumber), COALESCE(s.AmountInCompanyCodeCurrency, i.AmountInCompanyCodeCurrency), COALESCE(s.PaymentDueDate, i.PaymentDueDate), COALESCE(s.DocumentType, i.DocumentType), s.PaymentBlockReason, COALESCE(s.PurchasingDocument, i.PurchasingDocument)
FROM source_events s LEFT JOIN invoice_base i ON i.InvoiceNumber = s.InvoiceNumber AND i.CompanyCode = s.CompanyCode AND i.FiscalYear = s.FiscalYear
WHERE s.SourceEventType = 'REJECTED'
UNION ALL
SELECT s.InvoiceNumber, 'Invoice Data Updated', s.EventTime, s.UserName, s.CompanyCode, COALESCE(s.VendorNumber, i.VendorNumber), COALESCE(s.AmountInCompanyCodeCurrency, i.AmountInCompanyCodeCurrency), COALESCE(s.PaymentDueDate, i.PaymentDueDate), COALESCE(s.DocumentType, i.DocumentType), s.PaymentBlockReason, COALESCE(s.PurchasingDocument, i.PurchasingDocument)
FROM source_events s LEFT JOIN invoice_base i ON i.InvoiceNumber = s.InvoiceNumber AND i.CompanyCode = s.CompanyCode AND i.FiscalYear = s.FiscalYear
WHERE s.SourceEventType = 'DATA_UPDATED'
UNION ALL
SELECT s.InvoiceNumber, 'Payment Block Set', s.EventTime, s.UserName, s.CompanyCode, COALESCE(s.VendorNumber, i.VendorNumber), COALESCE(s.AmountInCompanyCodeCurrency, i.AmountInCompanyCodeCurrency), COALESCE(s.PaymentDueDate, i.PaymentDueDate), COALESCE(s.DocumentType, i.DocumentType), s.PaymentBlockReason, COALESCE(s.PurchasingDocument, i.PurchasingDocument)
FROM source_events s LEFT JOIN invoice_base i ON i.InvoiceNumber = s.InvoiceNumber AND i.CompanyCode = s.CompanyCode AND i.FiscalYear = s.FiscalYear
WHERE s.SourceEventType = 'PAYMENT_BLOCK_SET'
UNION ALL
SELECT s.InvoiceNumber, 'Payment Block Removed', s.EventTime, s.UserName, s.CompanyCode, COALESCE(s.VendorNumber, i.VendorNumber), COALESCE(s.AmountInCompanyCodeCurrency, i.AmountInCompanyCodeCurrency), COALESCE(s.PaymentDueDate, i.PaymentDueDate), COALESCE(s.DocumentType, i.DocumentType), s.PaymentBlockReason, COALESCE(s.PurchasingDocument, i.PurchasingDocument)
FROM source_events s LEFT JOIN invoice_base i ON i.InvoiceNumber = s.InvoiceNumber AND i.CompanyCode = s.CompanyCode AND i.FiscalYear = s.FiscalYear
WHERE s.SourceEventType = 'PAYMENT_BLOCK_REMOVED'
UNION ALL
SELECT
i.InvoiceNumber,
'Invoice Posted',
p.PostedAt,
p.PostedBy,
i.CompanyCode,
COALESCE(i.VendorNumber, j.VendorNumber),
COALESCE(i.AmountInCompanyCodeCurrency, j.AmountInCompanyCodeCurrency),
i.PaymentDueDate,
i.DocumentType,
CAST(NULL AS NVARCHAR(20)),
COALESCE(i.PurchasingDocument, j.PurchasingDocument)
FROM invoice_base i
INNER JOIN posted_accounting p ON p.InvoiceNumber = i.InvoiceNumber AND p.CompanyCode = i.CompanyCode AND p.FiscalYear = i.FiscalYear
LEFT JOIN journal_attributes j ON j.CompanyCode = p.CompanyCode AND j.FiscalYear = p.FiscalYear AND j.AccountingDocument = p.AccountingDocument
WHERE p.PostedAt IS NOT NULL
UNION ALL
SELECT s.InvoiceNumber, 'Payment Proposal Created', s.EventTime, s.UserName, s.CompanyCode, COALESCE(s.VendorNumber, i.VendorNumber), COALESCE(s.AmountInCompanyCodeCurrency, i.AmountInCompanyCodeCurrency), COALESCE(s.PaymentDueDate, i.PaymentDueDate), COALESCE(s.DocumentType, i.DocumentType), s.PaymentBlockReason, COALESCE(s.PurchasingDocument, i.PurchasingDocument)
FROM source_events s LEFT JOIN invoice_base i ON i.InvoiceNumber = s.InvoiceNumber AND i.CompanyCode = s.CompanyCode AND i.FiscalYear = s.FiscalYear
WHERE s.SourceEventType = 'PAYMENT_PROPOSAL_CREATED'
UNION ALL
SELECT s.InvoiceNumber, 'Payment Executed', s.EventTime, s.UserName, s.CompanyCode, COALESCE(s.VendorNumber, i.VendorNumber), COALESCE(s.AmountInCompanyCodeCurrency, i.AmountInCompanyCodeCurrency), COALESCE(s.PaymentDueDate, i.PaymentDueDate), COALESCE(s.DocumentType, i.DocumentType), s.PaymentBlockReason, COALESCE(s.PurchasingDocument, i.PurchasingDocument)
FROM source_events s LEFT JOIN invoice_base i ON i.InvoiceNumber = s.InvoiceNumber AND i.CompanyCode = s.CompanyCode AND i.FiscalYear = s.FiscalYear
WHERE s.SourceEventType = 'PAYMENT_EXECUTED'
UNION ALL
SELECT s.InvoiceNumber, 'Late Payment Executed', s.EventTime, s.UserName, s.CompanyCode, COALESCE(s.VendorNumber, i.VendorNumber), COALESCE(s.AmountInCompanyCodeCurrency, i.AmountInCompanyCodeCurrency), COALESCE(s.PaymentDueDate, i.PaymentDueDate), COALESCE(s.DocumentType, i.DocumentType), s.PaymentBlockReason, COALESCE(s.PurchasingDocument, i.PurchasingDocument)
FROM source_events s LEFT JOIN invoice_base i ON i.InvoiceNumber = s.InvoiceNumber AND i.CompanyCode = s.CompanyCode AND i.FiscalYear = s.FiscalYear
WHERE s.SourceEventType = 'PAYMENT_EXECUTED'
AND s.EventTime > CAST(COALESCE(s.PaymentDueDate, i.PaymentDueDate) AS TIMESTAMP)
UNION ALL
SELECT
i.InvoiceNumber,
'Invoice Reversed',
p.ReversalEventAt,
p.ReversalUser,
i.CompanyCode,
COALESCE(i.VendorNumber, j.VendorNumber),
COALESCE(i.AmountInCompanyCodeCurrency, j.AmountInCompanyCodeCurrency),
i.PaymentDueDate,
i.DocumentType,
CAST(NULL AS NVARCHAR(20)),
COALESCE(i.PurchasingDocument, j.PurchasingDocument)
FROM invoice_base i
INNER JOIN [Your verified reversal event source] p ON p.INV_DOC_NO = i.InvoiceNumber AND p.COMPANY_CODE = i.CompanyCode AND p.FISCAL_YEAR = i.FiscalYear
LEFT JOIN journal_attributes j ON j.CompanyCode = p.COMPANY_CODE AND j.FiscalYear = p.FISCAL_YEAR AND j.AccountingDocument = p.ACCOUNTING_DOCUMENT
WHERE p.ReversalEventAt IS NOT NULL
)
SELECT
InvoiceNumber,
ActivityName,
EventTime,
UserName,
CompanyCode,
VendorNumber,
AmountInCompanyCodeCurrency,
PaymentDueDate,
DocumentType,
PaymentBlockReason,
PurchasingDocument
FROM base_events
WHERE InvoiceNumber IS NOT NULL
AND EventTime IS NOT NULL
ORDER BY InvoiceNumber, EventTime, ActivityName; 步骤
- 访问ABAP编辑器:登录SAP S/4HANA系统,打开事务
SE38(ABAP编辑器)。 - 创建程序:在“程序”字段中输入新程序名称,例如
Z_PM_INVOICE_EXTRACT,然后点击“创建”。填写标题,将类型设置为“可执行程序”,并将其保存到适当的软件包中。 - 定义程序结构和选择屏幕:在编辑器中定义最终事件日志输出的数据结构,然后创建选择屏幕,允许用户输入发票录入日期范围、公司代码和凭证类型等参数,使程序具备可复用性和灵活性。
- 实现数据选择逻辑:编写ABAP SQL核心语句,从各类SAP表中选择数据。程序将依次查询所需的13项活动。
- 提取抬头和项目数据:对于“Invoice Document Created”和“Invoice Posted”等基础事件,从
RBKP(物流发票抬头)和BKPF(会计凭证抬头)等主表中选择数据。 - 提取变更凭证数据:对于“Payment Block Set”和“Payment Block Removed”等活动,查询变更凭证表
CDHDR(变更凭证抬头)和CDPOS(变更凭证项目)。您需要识别特定字段的变更,例如表BSEG中的ZLSPR。 - 提取付款数据:为捕获付款相关活动,查询
REGUP(付款程序已处理项目)获取付款建议,查询BSAK(已清账供应商项目)获取已执行付款。通过比较清账日期(AUGDT)与净到期日(ZFBDT)区分“Late Payment Executed”。 - 提取工作流数据:对于审批活动,查询SAP Business Workflow表,例如
SWW_WI2OBJ,将工作项关联到发票对象。此部分高度依赖具体工作流配置,可能需要大量调整。 - 统一为事件日志格式:对于每项选定活动,将数据格式化为统一的内部表结构。每行代表一个事件,必须包含案例标识(
InvoiceNumber)、ActivityName和EventTime,以及其他建议属性。 - 生成输出文件:使用ABAP语句
OPEN DATASET、TRANSFER和CLOSE DATASET,将最终内部表内容写入SAP应用服务器上的平面文件。建议使用逗号分隔的CSV格式。 - 调度和执行:测试时在前台执行程序(使用
F8)。生产运行时,使用事务SM36将其安排为后台作业,在业务低峰期运行,以避免影响系统性能。 - 获取并上传:使用事务
AL11进入保存文件的应用服务器目录,将文件下载到本地系统。上传到流程挖掘工具前,请确保文件采用UTF-8编码且格式正确。
配置
- 日期范围:根据发票录入日期(
RBKP-CPUDT)或过账日期(BKPF-BUDAT)定义具体提取日期范围。初次分析建议使用3至6个月的时间段,以确保数据量可控。 - 公司代码(BUKRS):必须按一个或多个公司代码进行筛选。在大型组织中提取所有公司代码的数据,可能导致运行时间过长并生成大型文件。
- 凭证类型(BLART):按相关凭证类型筛选,以隔离供应商发票。常见类型包括“RE”(发票,总额)和“KR”(供应商发票),有助于排除与分析无关的凭证。
- 供应商账户(LIFNR):程序可选择按特定供应商编号筛选,适用于定向分析或测试。
- 输出文件配置:程序应提供参数,用于定义应用服务器上的输出文件路径和字段分隔符,例如逗号或分号。
- 前提条件:执行程序的用户或系统账户需要具备创建和运行ABAP程序的开发权限(通过
SE38),以及对FI、MM和Basis表的广泛读取授权,包括BKPF、BSEG、RBKP、RSEG、CDHDR、CDPOS和工作流表。
a 示例查询 abap
REPORT Z_PM_INVOICE_EXTRACT.
* --- Internal table structure for the final event log
TYPES: BEGIN OF ty_s_event_log,
invoicenumber TYPE char25,
activityname TYPE char50,
eventtime TYPE char19, "YYYY-MM-DD HH:MM:SS
username TYPE sy-uname,
companycode TYPE bukrs,
vendornumber TYPE lifnr,
amountincompanycodecurrency TYPE wrbtr,
paymentduedate TYPE char10, "YYYY-MM-DD
documenttype TYPE blart,
paymentblockreason TYPE char1,
purchasingdocument TYPE ebeln,
END OF ty_s_event_log.
DATA: lt_event_log TYPE STANDARD TABLE OF ty_s_event_log.
DATA: ls_event_log TYPE ty_s_event_log.
* --- Selection Screen for user inputs
PARAMETERS: p_path TYPE string DEFAULT '/usr/sap/tmp/invoice_events.csv'.
SELECT-OPTIONS: s_erdat FOR sy-datum OBLIGATORY, " Entry Date
s_bukrs FOR bkpf-bukrs OBLIGATORY, " Company Code
s_blart FOR bkpf-blart. " Document Type
START-OF-SELECTION.
* --- 1. Invoice Document Created (from Logistics Invoice Verification)
SELECT CONCAT( rbkp~belnr, rbkp~gjahr ) AS invoicenumber,
'Invoice Document Created' AS activityname,
CONCAT( rbkp~cpudt, rbkp~cputm ) AS eventtime,
rbkp~usnam AS username,
rbkp~bukrs AS companycode,
rbkp~lifnr AS vendornumber,
rbkp~rmwwr AS amountincompanycodecurrency,
'' AS paymentduedate,
rbkp~blart AS documenttype,
rbkp~zuonr AS paymentblockreason,
'' AS purchasingdocument
FROM rbkp
INTO TABLE @DATA(lt_created)
WHERE rbkp~cpudt IN @s_erdat
AND rbkp~bukrs IN @s_bukrs
AND rbkp~blart IN @s_blart.
LOOP AT lt_created INTO DATA(ls_created).
ls_event_log-invoicenumber = ls_created-invoicenumber.
ls_event_log-activityname = ls_created-activityname.
ls_event_log-eventtime = |{ ls_created-eventtime(8) } { ls_created-eventtime+8(2) }:{ ls_created-eventtime+10(2) }:{ ls_created-eventtime+12(2) }|.
ls_event_log-username = ls_created-username.
ls_event_log-companycode = ls_created-companycode.
ls_event_log-vendornumber = ls_created-vendornumber.
ls_event_log-amountincompanycodecurrency = ls_created-amountincompanycodecurrency.
ls_event_log-paymentduedate = ''.
ls_event_log-documenttype = ls_created-documenttype.
ls_event_log-paymentblockreason = ''.
ls_event_log-purchasingdocument = ls_created-purchasingdocument.
APPEND ls_event_log TO lt_event_log.
ENDLOOP.
* --- 2. Invoice Parked (assuming status 'A' or 'B' in RBKP)
SELECT CONCAT( belnr, gjahr ) AS invoicenumber,
'Invoice Parked' AS activityname,
CONCAT( cpudt, cputm ) AS eventtime,
usnam AS username,
bukrs AS companycode,
lifnr AS vendornumber,
rmwwr AS amountincompanycodecurrency,
'' AS paymentduedate,
blart AS documenttype,
'' AS paymentblockreason,
'' AS purchasingdocument
FROM rbkp
INTO TABLE @DATA(lt_parked)
WHERE rbstat IN ('A', 'B')
AND cpudt IN @s_erdat
AND bukrs IN @s_bukrs
AND blart IN @s_blart.
LOOP AT lt_parked INTO DATA(ls_parked).
ls_event_log-invoicenumber = ls_parked-invoicenumber.
ls_event_log-activityname = ls_parked-activityname.
ls_event_log-eventtime = |{ ls_parked-eventtime(8) } { ls_parked-eventtime+8(2) }:{ ls_parked-eventtime+10(2) }:{ ls_parked-eventtime+12(2) }|.
ls_event_log-username = ls_parked-username.
ls_event_log-companycode = ls_parked-companycode.
ls_event_log-vendornumber = ls_parked-vendornumber.
ls_event_log-amountincompanycodecurrency = ls_parked-amountincompanycodecurrency.
ls_event_log-paymentduedate = ''.
ls_event_log-documenttype = ls_parked-documenttype.
ls_event_log-paymentblockreason = ''.
ls_event_log-purchasingdocument = ''.
APPEND ls_event_log TO lt_event_log.
ENDLOOP.
* --- 3, 4, 5. Sent For Approval, Approved, Rejected (Placeholder logic, needs adaptation)
* --- This logic is a generic template for SAP Business Workflow.
* --- Your implementation will vary. You must identify the correct workflow tasks.
SELECT obj.instid, wi.wi_cd, wi.wi_ct, wi.wi_stat, wi.wi_aagent
FROM sww_wi2obj AS obj
JOIN swwlog AS wi ON obj~instid = wi~wi_id
INTO TABLE @DATA(lt_workflow)
WHERE obj~typeid = 'BUS2081' " Business Object for Incoming Invoice
AND obj~catid = 'BO'
AND wi~wi_cd IN s_erdat.
LOOP AT lt_workflow INTO DATA(ls_workflow).
* --- This is a placeholder, adapt task IDs and logic
CASE ls_workflow-wi_stat.
WHEN 'STARTED'.
ls_event_log-activityname = 'Invoice Sent For Approval'.
WHEN 'COMPLETED'.
ls_event_log-activityname = 'Invoice Approved'.
WHEN 'CANCELLED'.
ls_event_log-activityname = 'Invoice Rejected'.
WHEN OTHERS.
CONTINUE.
ENDCASE.
* --- Code to get invoice details based on ls_workflow-instid needed here
* --- ... appending to lt_event_log ...
ENDLOOP.
* --- 6, 7, 8. Payment Block Set/Removed, Data Updated (from Change Docs)
SELECT h~objectid, h~username, h~udate, h~utime, p~fname, p~value_new, p~value_old
FROM cdhdr AS h
JOIN cdpos AS p ON h~objectclas = p~objectclas AND h~objectid = p~objectid AND h~changenr = p~changenr
INTO TABLE @DATA(lt_changes)
WHERE h~objectclas = 'BELEGV'
AND h~udate IN s_erdat.
LOOP AT lt_changes INTO DATA(ls_change).
ls_event_log-invoicenumber = |{ ls_change-objectid+10(10) }{ ls_change-objectid(4) }|.
ls_event_log-username = ls_change-username.
ls_event_log-eventtime = |{ ls_change-udate } { ls_change-utime(2) }:{ ls_change-utime+2(2) }:{ ls_change-utime+4(2) }|.
IF ls_change-fname = 'ZLSPR'. " Payment Block
IF ls_change-value_old IS INITIAL AND ls_change-value_new IS NOT INITIAL.
ls_event_log-activityname = 'Payment Block Set'.
ls_event_log-paymentblockreason = ls_change-value_new.
ELSEIF ls_change-value_old IS NOT INITIAL AND ls_change-value_new IS INITIAL.
ls_event_log-activityname = 'Payment Block Removed'.
ls_event_log-paymentblockreason = ''.
ELSE.
CONTINUE.
ENDIF.
ELSE.
ls_event_log-activityname = 'Invoice Data Updated'.
ENDIF.
* --- Need to select other attributes based on invoice number
* --- ... appending to lt_event_log ...
ENDLOOP.
* --- 9. Invoice Posted
SELECT CONCAT( bkpf~belnr, bkpf~gjahr ) AS invoicenumber,
'Invoice Posted' AS activityname,
CONCAT( bkpf~cpudt, bkpf~cputm ) AS eventtime,
bkpf~usnam AS username,
bkpf~bukrs AS companycode,
bseg~lifnr AS vendornumber,
bseg~wrbtr AS amountincompanycodecurrency,
bseg~zfBDT AS paymentduedate,
bkpf~blart AS documenttype,
bseg~zlspr AS paymentblockreason,
bseg~ebeln AS purchasingdocument
FROM bkpf
JOIN bseg ON bkpf~bukrs = bseg~bukrs AND bkpf~belnr = bseg~belnr AND bkpf~gjahr = bseg~gjahr
INTO TABLE @DATA(lt_posted)
WHERE bkpf~cpudt IN @s_erdat
AND bkpf~bukrs IN @s_bukrs
AND bkpf~blart IN @s_blart
AND bseg~koart = 'K'. " Vendor line
LOOP AT lt_posted INTO DATA(ls_posted).
ls_event_log-invoicenumber = ls_posted-invoicenumber.
ls_event_log-activityname = ls_posted-activityname.
ls_event_log-eventtime = |{ ls_posted-eventtime(8) } { ls_posted-eventtime+8(2) }:{ ls_posted-eventtime+10(2) }:{ ls_posted-eventtime+12(2) }|.
ls_event_log-username = ls_posted-username.
ls_event_log-companycode = ls_posted-companycode.
ls_event_log-vendornumber = ls_posted-vendornumber.
ls_event_log-amountincompanycodecurrency = ls_posted-amountincompanycodecurrency.
ls_event_log-paymentduedate = ls_posted-paymentduedate.
ls_event_log-documenttype = ls_posted-documenttype.
ls_event_log-paymentblockreason = ls_posted-paymentblockreason.
ls_event_log-purchasingdocument = ls_posted-purchasingdocument.
APPEND ls_event_log TO lt_event_log.
ENDLOOP.
* --- 10. Payment Proposal Created
SELECT CONCAT( regup~belnr, regup~gjahr ) AS invoicenumber,
'Payment Proposal Created' AS activityname,
CONCAT( reguh~erfdt, reguh~erfzt ) AS eventtime,
reguh~erfbu AS username,
regup~bukrs AS companycode,
regup~lifnr AS vendornumber,
regup~wrbtr AS amountincompanycodecurrency,
'' AS paymentduedate,
regup~blart AS documenttype,
'' AS paymentblockreason,
'' AS purchasingdocument
FROM regup
JOIN reguh ON regup~laufd = reguh~laufd AND regup~laufi = reguh~laufi
INTO TABLE @DATA(lt_proposal)
WHERE reguh~erfdt IN @s_erdat
AND regup~bukrs IN @s_bukrs.
LOOP AT lt_proposal INTO DATA(ls_proposal).
ls_event_log-invoicenumber = ls_proposal-invoicenumber.
ls_event_log-activityname = ls_proposal-activityname.
ls_event_log-eventtime = |{ ls_proposal-eventtime(8) } { ls_proposal-eventtime+8(2) }:{ ls_proposal-eventtime+10(2) }:{ ls_proposal-eventtime+12(2) }|.
ls_event_log-username = ls_proposal-username.
ls_event_log-companycode = ls_proposal-companycode.
ls_event_log-vendornumber = ls_proposal-vendornumber.
ls_event_log-amountincompanycodecurrency = ls_proposal-amountincompanycodecurrency.
APPEND ls_event_log TO lt_event_log.
ENDLOOP.
* --- 11, 12. Payment Executed / Late Payment Executed
SELECT CONCAT( belnr, gjahr ) AS invoicenumber,
augdt,
zfBDT
FROM bsak
INTO TABLE @DATA(lt_cleared)
WHERE augdt IN @s_erdat
AND bukrs IN @s_bukrs.
LOOP AT lt_cleared INTO DATA(ls_cleared).
IF ls_cleared-augdt > ls_cleared-zfbdt.
ls_event_log-activityname = 'Late Payment Executed'.
ELSE.
ls_event_log-activityname = 'Payment Executed'.
ENDIF.
ls_event_log-invoicenumber = ls_cleared-invoicenumber.
ls_event_log-eventtime = |{ ls_cleared-augdt } 00:00:00|.
* --- Need to select other attributes based on invoice number
* --- ... appending to lt_event_log ...
ENDLOOP.
* --- 13. Invoice Reversed
SELECT CONCAT( stblg, stjah ) AS invoicenumber,
'Invoice Reversed' AS activityname,
CONCAT( cpudt, cputm ) AS eventtime,
usnam AS username,
bukrs AS companycode,
'' AS vendornumber,
'' AS amountincompanycodecurrency,
'' AS paymentduedate,
blart AS documenttype,
'' AS paymentblockreason,
'' AS purchasingdocument
FROM bkpf
INTO TABLE @DATA(lt_reversed)
WHERE stblg IS NOT NULL
AND cpudt IN @s_erdat
AND bukrs IN @s_bukrs.
LOOP AT lt_reversed INTO DATA(ls_reversed).
ls_event_log-invoicenumber = ls_reversed-invoicenumber.
ls_event_log-activityname = ls_reversed-activityname.
ls_event_log-eventtime = |{ ls_reversed-eventtime(8) } { ls_reversed-eventtime+8(2) }:{ ls_reversed-eventtime+10(2) }:{ ls_reversed-eventtime+12(2) }|.
ls_event_log-username = ls_reversed-username.
ls_event_log-companycode = ls_reversed-companycode.
APPEND ls_event_log TO lt_event_log.
ENDLOOP.
* --- Write internal table to CSV file
OPEN DATASET p_path FOR OUTPUT IN TEXT MODE ENCODING UTF-8.
IF sy-subrc <> 0.
MESSAGE 'Error opening file.' TYPE 'E'.
ENDIF.
DATA: lv_line TYPE string.
FIELD-SYMBOLS: <fs_any> TYPE any.
* --- Header row
lv_line = 'InvoiceNumber,ActivityName,EventTime,UserName,CompanyCode,VendorNumber,AmountInCompanyCodeCurrency,PaymentDueDate,DocumentType,PaymentBlockReason,PurchasingDocument'.
TRANSFER lv_line TO p_path.
LOOP AT lt_event_log INTO ls_event_log.
CLEAR lv_line.
DO.
ASSIGN COMPONENT sy-index OF STRUCTURE ls_event_log TO <fs_any>.
IF sy-subrc <> 0.
EXIT.
ENDIF.
IF sy-index = 1.
lv_line = <fs_any>.
ELSE.
CONCATENATE lv_line <fs_any> INTO lv_line SEPARATED BY ','.
ENDIF.
ENDDO.
TRANSFER lv_line TO p_path.
ENDLOOP.
CLOSE DATASET p_path.
WRITE: / 'Extraction complete. File saved to:', p_path. 立即优化SAP S/4HANA中的P2P发票处理
精准定位瓶颈,将发票周期时间缩短30%或更多。
无需信用卡,几分钟即可完成设置。