您的 Record to Report 总账分录数据模板
您的 Record to Report 总账分录数据模板
- 建议收集的属性
- 需要追踪的关键活动
- 数据抽取实用指南
Record to Report - 日记账分录属性
| 名称 | 描述 | ||
|---|---|---|---|
| Event 时间 EventTime | 指示日记账分录特定活动发生的时间戳。 | ||
| 描述 Event Time 是业务活动在系统中执行并记录的精确日期和时间。case 中的每项活动都有其专属的时间戳,从而形成按时间顺序排列的 event 序列。 此属性对于所有基于时间的流程分析都至关重要。它用于计算 cycle times、活动间隔时长、等待时间,以及了解工作的时间分布。准确的时间戳对于构建可靠的流程模型以及计算诸如“审批周期”等关键绩效指标不可或缺。 为何重要 它提供按时间顺序排列的 event 序列,这对于计算所有基于时长的指标以及了解流程时间线至关重要。 获取方式 源自更改凭证日志 (CDHDR-UDATE, CDHDR-UTIME)、工作流日志或表(如 BKPF)上的创建/输入时间戳 (CPUDT, CPUTM)。 示例 2023-10-26T10:05:00Z2023-11-15T14:30:15Z2024-01-20T09:00:45Z | |||
| 日记账分录 ID JournalEntryId | 财务日记账分录的唯一标识符,作为流程的主要 case 标识符。 | ||
| 描述 日记账分录 ID 是在 SAP S/4HANA 中创建每张会计凭证时分配的唯一编号。此标识符对于跟踪日记账分录的完整生命周期至关重要,涵盖了从初始创建或预制、审批工作流,到最终过账以及可能的冲销或清账全过程。 在流程挖掘分析中,此 ID 用于将所有相关活动关联到一个 case(案例)中。通过将事件归组在同一个日记账分录 ID 下,分析人员可以重构端到端的流程流,衡量周期时间,并识别每笔特定财务交易的变体或瓶颈。它是构建整个流程视图的基础属性。 为何重要 此标识符连接了所有相关的流程步骤,使得分析每个日记账分录的端到端旅程成为可能。 获取方式 这是一个复合键,通常通过串联公司代码 (BKPF-BUKRS)、凭证编号 (BKPF-BELNR) 和会计年度 (BKPF-GJAHR) 形成。 示例 1000-1000000001-20231710-1900000055-20242000-2100003412-2023 | |||
| 活动 ActivityName | 在日记账分录流程中的特定点发生的业务活动的名称。 | ||
| 描述 Activity(活动)代表日记账分录生命周期中的特定步骤或事件,例如“日记账分录已创建”、“日记账已提交审核”或“日记账分录已过账”。这些活动通常源自系统中记录的更改日志、状态更新或事务代码。 分析活动可以实现流程流向的可视化,识别常用路径并发现偏离标准流程的情况。它是计算活动频率、步骤间等待时间和一致性率等指标的基础。 为何重要 它定义了流程中的步骤,支持流程图的可视化以及 workflow 模式的分析。 获取方式 源自抬头/项目表中的状态字段 (例如 BKPF-BSTAT)、更改凭证日志 (CDHDR/CDPOS) 和 workflow 日志等多种来源。 示例 已创建日记账分录日记账分录已预制日记账已提交审核会计分录已批准日记账分录已过账 | |||
| 公司代码 CompanyCode | 发布日记账分录的公司或法人实体的唯一标识符。 | ||
| 描述 公司代码是 SAP 财务模块中的基本组织单位,代表生成财务报表的独立法人实体。每条日记账分录都会分配给特定的公司代码。 该属性对于细分和比较组织内不同部门的流程绩效至关重要。分析人员可以使用它来过滤特定法人的流程视图、比较各公司代码之间的拒绝率,或识别特定地区的流程差异。 为何重要 允许在组织内的不同法定实体或业务单位之间筛选和比较会计分录流程。 获取方式 SAP S/4HANA 表 BKPF,字段 BUKRS(公司代码)。 示例 10001710US01 | |||
| 创建用户 CreatedByUser | 创建日记账分录的人员的用户 ID。 | ||
| 描述 此属性存储通过创建初始凭证启动日记账分录流程的用户唯一标识符。这可以是会计、业务用户,或者是自动分录的系统 ID。 按创建者分析流程有助于识别与特定用户或团队相关的模式。如果某些用户的拒绝率较高,则可能揭示培训需求;或者突出表现优异的个人。这对于“用户活动与吞吐量”仪表板至关重要。 为何重要 将流程活动归因于特定用户,从而实现绩效分析、工作量平衡以及识别培训需求。 获取方式 SAP S/4HANA 表 BKPF,字段 USNAM(用户名)。 示例 ABROWNCJONESBATCH_USER | |||
| 日记账分录类型 JournalEntryType | 根据业务目的将会计分录分类,例如资产过账、供应商发票或总账分录。 | ||
| 描述 日记账分录类型(在 SAP 术语中称为凭证类型)是将会计凭证分类的键。它控制着分配给凭证的编号范围以及允许过账的账户类型等。 按日记账分录类型分析流程对于理解特定背景下的行为至关重要。例如,简单应计项目(SA 类型)的审批流程可能比复杂的资产购置(AA 类型)简单得多。该维度是“按分录类型划分的合规性”仪表板的关键。 为何重要 按业务上下文对分录进行分类,以便分析不同类型财务交易的流程变体和性能。 获取方式 SAP S/4HANA 表 BKPF,字段 BLART(凭证类型)。 示例 SAKRAA | |||
| 本币金额 AmountInLocalCurrency | 以公司代码本位币表示的日记账分录总额。 | ||
| 描述 此属性代表日记账分录的财务规模。它通常是凭证中所有借方或贷方行项目绝对值的总和,并转换为公司代码的本位币。 按金额分析支持根据财务影响对流程进行细分。例如,高额分录可能比低额分录遵循更严格的审批流程。这有助于将流程改进工作优先放在财务风险最高的交易上。 为何重要 提供分录的财务价值,从而能够分析流程行为如何随所涉及的货币金额而变化。 获取方式 通过汇总给定会计分录 (BELNR) 在行项目表 BSEG(字段 DMBTR)中的金额并转换为正值得出。 示例 1500.75125000.0050.20 | |||
| 过账日期 PostingDate | 日记账分录记录在总账中的日期,影响财务期间。 | ||
| 描述 过账日期决定了交易将出现在财务报表中的会计期间。这是会计核算中的关键日期,可能与凭证创建或录入系统的日期不同。 在流程挖掘中,过账日期用于基于时间的群组分析,例如比较月末结账流程或分析不同财务期间的绩效趋势。它还用于衡量分录创建到实际财务过账之间的延迟。 为何重要 对于财务上下文至关重要,它允许分析特定会计期间(如月末或年末)的流程绩效。 获取方式 SAP S/4HANA 表 BKPF,字段 BUDAT(凭证中的过账日期)。 示例 2023-10-312023-11-012024-02-29 | |||
| 事务代码 TransactionCode | 用于创建或更改日记账分录的 SAP 事务代码。 | ||
| 描述 事务代码 (T-Code) 是在 SAP 中识别特定功能或程序的快捷方式。对于日记账分录,不同的 T-Code 可以指示分录的创建方式,例如手工总账过账的 FB01、预制的 FV50,或用于系统生成分录的自动代码。 该属性是判断活动是由用户手动执行还是由系统自动执行的重要依据。它是计算“手工过账率” KPI 和识别自动化机会的关键。 为何重要 指示分录的处理方式(例如,手动 vs 自动),这对于自动化分析和理解流程变体至关重要。 获取方式 SAP S/4HANA 表 BKPF,字段 TCODE(事务代码)。 示例 FB01FV50F-02 | |||
| 会计年度 FiscalYear | 日记账分录所属的会计年度。 | ||
| 描述 会计年度与公司代码、凭证编号共同构成日记账分录的唯一键。它代表凭证所属的财务年度。 在分析中,会计年度用于长期趋势分析,并确保 case 标识符的唯一性。通过比较不同会计年度的流程指标,可以揭示绩效随时间的改进或退化情况。 为何重要 为唯一识别凭证提供关键组件,并支持跨年度流程绩效分析。 获取方式 SAP S/4HANA 表 BKPF,字段 GJAHR(会计年度)。 示例 202320242022 | |||
| 最后数据更新 LastDataUpdate | 表示该记录的数据上次从源系统刷新的时间戳。 | ||
| 描述 此属性记录从源系统提取或更新数据的最新日期和时间。它提供了所分析数据新鲜度的透明信息。 了解最后更新时间对于掌握流程分析的实效性非常重要。这有助于用户正确解读仪表板和 KPI,明确他们看到的是近乎实时的数据还是过去某段时间的快照。 为何重要 指示数据的时效性,确保用户了解流程分析的最新程度。 获取方式 这是一个元数据属性,通常在数据摄取流水线期间生成并标记在每条记录上。 示例 2024-03-10T02:00:00Z2024-03-11T02:00:00Z2024-03-12T02:00:00Z | |||
| 冲销原因 ReversalReason | 一个代码,指示已过账会计分录被冲销的原因。 | ||
| 描述 当已过账的日记账分录有误时,无法直接删除,必须通过新凭证进行冲销。冲销原因代码解释了采取该操作的原因,例如过账日期或金额错误。 分析冲销原因有助于识别 Record to Report 流程中错误的根本原因。某一原因出现频率过高可能指向系统性问题,如培训不足或控制失效,需要解决这些问题以提高首次质量。 为何重要 有助于诊断导致冲销的错误根本原因,提供减少返工和提高流程质量所需的洞察。 获取方式 SAP S/4HANA 表 BKPF,字段 STGRD(冲销原因)。 示例 010205 | |||
| 单据状态 DocumentStatus | 日记账分录的当前处理状态,如已预制、已过账或已清账。 | ||
| 描述 凭证状态指示日记账分录在其生命周期中所处的状态。例如,“已预制”凭证已保存但尚未过账到总账,而“已过账”凭证则已最终确定。 分析状态有助于了解工作流并识别瓶颈。大量凭证长时间处于“已预制”或“待审批”状态可能预示流程低效。它也是推导流程活动的关键来源。 为何重要 提供日记账分录生命周期的快照,帮助识别排队和瓶颈环节。 获取方式 SAP S/4HANA 表 BKPF,字段 BSTAT(凭证状态)。 示例 VAB | |||
| 审批周期时间 ApprovalCycleTime | 日记账分录从提交审批到被批准或拒绝所经过的时间。 | ||
| 描述 此计算指标专门关注审批阶段的时长。它衡量从“日记账已提交审核”活动到随后的“日记账分录已批准”或“日记账分录被拒绝”活动之间的时间。 该 KPI 对于识别审批工作流中的瓶颈至关重要。审批周期过长会严重延误整个流程。按审批人、公司代码或分录类型分析此指标,可以发现具体的改进领域。 为何重要 隔离审批步骤的持续时间,有助于锁定并解决审核及审批 workflow 中的瓶颈。 获取方式 通过计算“分录已提交审核”event 与“会计分录已批准”或“会计分录已拒绝”event 之间的时间差得出。 示例 1 天 2 小时4 小时 25 分钟5 天 0 小时 | |||
| 审批用户 ApproverUser | 批准或拒绝日记账分录的人员的用户 ID。 | ||
| 描述 此属性识别负责审核已提交日记账分录并做出决定的用户。在多级审批工作流中,一笔分录可能有多个审批人。 此信息对于深入分析审批流程至关重要。它有助于衡量不同审批人的工作量、计算个人审批时间,并识别审批链中的瓶颈。它直接支持“用户活动与吞吐量”仪表板。 为何重要 识别负责审批的个人,从而能够分析审批工作量、绩效和瓶颈。 获取方式 通过跟踪谁执行了审批步骤,源自工作流日志(如 SWW_WI2OBJ, SWWLOG)或更改凭证表 (CDHDR/CDPOS)。 示例 DMILLERFWHITEKCHEN | |||
| 总周期时间 TotalCycleTime | 日记账分录从第一个活动创建到最后一个活动完成的总时长。 | ||
| 描述 此计算指标衡量每个 case 日记账分录流程的端到端时长。它是最后观察到的活动的时间戳与第一个活动的时间戳之差。 总周期时间是衡量整体流程效率的主要 KPI。它提供了流程绩效的高层级视图,并在仪表板中用于跟踪长期趋势。分析导致长周期的原因通常是流程改进的起点。 为何重要 衡量端到端流程时长,为整体流程效率和速度提供关键绩效指标 (KPI)。 获取方式 通过从每个唯一 JournalEntryId 的最大 EventTime 中减去最小 EventTime 得出。 示例 2 天 4 小时 30 分钟8 小时 15 分钟15天2小时 | |||
| 是否为手动过账 IsManualPosting | 一个布尔标志,指示会计分录是否由用户手动过账。 | ||
| 描述 该属性识别通过人工干预过账的日记账分录,以区别于通过系统作业或接口自动过账的分录。它通常源自用于凭证过账的事务代码。 此标记用于计算“手工过账率” KPI,帮助企业跟踪其在 Record to Report 流程自动化方面的进展。通过过滤手工过账的分录,分析人员可以识别仍需人工参与的特定场景,并评估其自动化潜力。 为何重要 区分人工和系统驱动的过账,这对于衡量自动化水平和识别自动化机会至关重要。 获取方式 这是根据 TransactionCode 推导出的计算属性。使用预定义的手工事务代码列表(如 'FB01', 'F-02')将此标记设置为 'true'。 示例 truefalse | |||
| 是否返工 IsRework | 一个布尔标志,指示会计分录是否经历过返工,例如在被拒绝后进行更正。 | ||
| 描述 此计算属性标记偏离了理想“快乐路径”流程的日记账分录。如果 case 中出现“日记账分录被拒绝”或“日记账分录已更正”等活动,该属性通常设置为 true。 此标记简化了流程效率分析。它支持快速计算“返工率” KPI,并能直接比较有无返工 case 之间的周期时间和成本。识别返工的诱因是许多流程改进计划的首要目标。 为何重要 标记需要更正或额外循环的 case,从而实现流程低效环节的轻松量化和根本原因分析。 获取方式 这是根据 case 中的活动序列推导出的计算属性。如果存在诸如“日记账分录被拒绝”之类的活动,则将其标记为“true”。 示例 truefalse | |||
| 源系统 SourceSystem | 识别提取会计分录数据的源系统。 | ||
| 描述 此属性指定日记账分录数据的原始记录系统。对于拥有多个 ERP 实例或混合使用旧系统与现代系统的公司,这有助于区分数据源。 在分析中,它可用于比较不同系统间的流程绩效,或过滤特定来源的数据。这对于数据治理以及确保理解数据背景非常重要。 为何重要 提供关于数据来源的上下文,这在多系统环境中对于准确的流程分析和比较至关重要。 获取方式 这通常是数据提取期间添加的静态值,用于标识具体的 SAP S/4HANA 实例(例如 SID 或逻辑系统名称)。 示例 S4H_PROD_100ECC_FIN_200S4C_US_EAST | |||
| 结束时间 EndTime | 指示活动完成时间的时间戳。 | ||
| 描述 End Time(结束时间)标志着活动的完成。在许多事件日志中,活动的开始时间和结束时间是相同的,代表一个瞬间发生的事件。然而,对于具有可测量持续时间的活动(如用户正在审核凭证),该属性可以捕捉到这段时长。 拥有明确的结束时间可以更精确地计算活动处理时间与等待时间。它有助于区分任务被主动处理的时间与在队列中闲置的时间。 为何重要 支持精确计算活动处理时间,将实际工作时间与空闲等待时间分开。 获取方式 对于原子事件,通常与 StartTime 相同。对于持续性活动,它可能源自工作流日志或根据后续事件计算得出。 示例 2023-10-26T10:05:00Z2023-11-15T14:45:20Z2024-01-20T09:10:30Z | |||
Record to Report - 日记账分录活动
| 活动 | 描述 | ||
|---|---|---|---|
| 会计分录已批准 | 日记账分录获得授权经理的最终审批,确认其有效性和准确性。此活动是凭证过账到总账前的最后一道关口。 | ||
| 为何重要 这是一个结束审批周期的关键里程碑。到达这一步所需的时间是整个流程时长的主要组成部分,也是审批效率的关键指标。 获取方式 此事件是从显示最终审批步骤的工作流日志或凭证上的状态更改推断出来的。审批者的用户 ID 和时间戳可以从工作流数据或更改日志中获取。 捕获 在 workflow 日志中识别最终批准步骤的时间戳,或在更改凭证中识别状态更改为“已批准”的时间戳。 事件类型 inferred | |||
| 已创建日记账分录 | 此活动标志着系统中日记账分录凭证的初始创建。记录在抬头表 (BKPF) 中生成,但尚未过账到总账。这是日记账分录生命周期的起点。 | ||
| 为何重要 这是流程的主要启动事件。分析从该事件到过账的时间对于衡量总周期时间和识别初始数据录入延迟至关重要。 获取方式 可以使用给定凭证编号 (BELNR) 的创建日期 (CPUDT) 和创建时间 (CPUTM) 字段,从 SAP 表 BKPF 中显式获取此事件。 捕获 使用 BKPF-CPUDT 和 BKPF-CPUTM 作为事件时间戳。 事件类型 explicit | |||
| 日记账分录冲销已处理 | 通过创建具有相反过账金额的新凭证来冲销先前已过账的会计分录。采取此操作是为了更正已过账凭证中的错误,这是一项明确且可审计的交易。 | ||
| 为何重要 冲销表示已过账凭证存在错误。高冲销率暗示审批流程或数据录入质量存在深层问题,跟踪这一指标有助于提高首次准确率。 获取方式 冲销是一个显性事件。新冲销凭证的抬头 (BKPF) 在“冲销凭证编号” (STBLG) 字段中包含对原始凭证的引用。新凭证的过账日期即为事件时间。 捕获 识别 BKPF 中填充了 STBLG 字段的凭证。event 时间戳是冲销凭证的过账日期。 事件类型 explicit | |||
| 日记账分录已清账 | 会计分录中的未清项行被另一笔过账抵消,例如支付清账发票。此活动标志着特定行项目的对账完成,从而有效地结清了这些项目。 | ||
| 为何重要 此活动代表许多日记账分录的最终对账步骤,特别是涉及暂记账户或未清项管理账户的分录。分析从过账到清账的时间有助于衡量对账效率。 获取方式 此事件是从行项目表(BSEG 或 ACDOCA 视图)推断出来的。当行项目清账时,该行的清账日期 (AUGDT) 和清账凭证 (AUGBL) 字段将被填充。 捕获 使用行项目的清账日期 (BSEG-AUGDT) 作为事件时间戳。 事件类型 inferred | |||
| 日记账分录已过账 | 日记账分录正式记录在总账中,影响公司的财务报表。此时凭证成为永久性的财务记录。 | ||
| 为何重要 这是主要的成功里程碑,标志着核心处理周期的结束。分析已过账分录的吞吐量以及到达此阶段的时间是基础的流程挖掘指标。 获取方式 这是由 BKPF 表中的过账日期 (BUDAT) 标记的显式事件。已过账凭证的凭证状态 (BSTAT) 为空,区别于预制 ('V') 或保留 ('D') 凭证。 捕获 使用过账日期 (BKPF-BUDAT) 和录入日期 (BKPF-CPUDT) 为事件标注时间戳。空的 BKPF-BSTAT 表示已过账凭证。 事件类型 explicit | |||
| 日记账已提交审核 | 日记账分录的创建者正式提交凭证进入审核和审批工作流。此活动代表从数据录入到正式控制流程的移交,标志着审批周期的开始。 | ||
| 为何重要 这标志着审批周期的开始。从这一时间点衡量到最终批准或拒绝,有助于专门隔离审核和审批阶段的瓶颈。 获取方式 这通常是从链接到业务对象的工作流日志(表 SWW_WIHEAD, SWWLOG)中捕获的。也可以从凭证抬头 (BKPF) 自定义字段中的状态更改中推断出来。 捕获 工作流项目创建的时间戳,或状态字段更改为“已提交”或“审核中”的时间戳。 事件类型 inferred | |||
| 已识别手工过账 | 日记账分录是使用手工事务代码过账的,而不是通过自动接口或批处理作业。这不属于时间性事件,而是过账活动的一种分类。 | ||
| 为何重要 识别手动过账对于自动化倡议至关重要。手动过账率高表明可以通过集成子系统或使用自动过账程序来简化流程。 获取方式 这通过分析凭证抬头表 (BKPF) 中的事务代码 (TCODE) 字段计算得出。使用已知的手工 T-Code 列表(如 FB01, F-02, FB50)对分录进行分类。 捕获 过账时根据 BKPF-TCODE 对照预定义的手动事务代码列表对 event 进行分类。 事件类型 calculated | |||
| 已附加支持文档 | 用户将会计分录与一个或多个支持性文件(如发票或电子表格)相关联。这通常是为了在审核和审计过程中为财务交易提供证据和上下文。 | ||
| 为何重要 确保在审核前附加相关文档对于合规性和审批效率至关重要。此活动有助于衡量对文档政策的遵循程度及其对审批 cycle times 的影响。 获取方式 这通常是通过检查经由通用对象服务 (GOS) 链接的附件的创建时间戳来推断的。SRGBTBREL 表将业务对象(如 BKPF 凭证)与附件关联起来。 捕获 查询 GOS 附件表(如 SRGBTBREL)中与 BKPF 对象的链接,并使用附件创建时间戳。 事件类型 inferred | |||
| 日记账分录已更正 | 用户在日记账分录被拒绝或退回修改后对其进行修改。这代表了在重新提交前,为解决审核过程中发现的问题而投入的返工工作。 | ||
| 为何重要 此活动量化了返工循环。分析修正的频率和时长有助于找准低效的根源,并突出培训和流程澄清的机会。 获取方式 这可以通过跟踪 BKPF 表中先前处于“已拒绝”状态凭证的“最后更改日期” (AEDAT) 来推断。更改凭证可以提供有关具体更改内容的更详细信息。 捕获 对于拒绝事件后所做的更改,请使用更改凭证抬头 (CDHDR-UDATE) 中的时间戳。 事件类型 inferred | |||
| 日记账分录已预制 | 用户在不进行过账的情况下保存不完整的会计分录,以便稍后完成或审核。这是一种明确的操作,会创建一个具有“预制”状态的凭证抬头记录,使其保持在未过账状态。 | ||
| 为何重要 预制(Parking)是提交前的常见步骤。跟踪预制状态的时长有助于识别正式审核和审批流程开始前,在数据完善和准备阶段发生的延迟。 获取方式 在 BKPF 表中,预制凭证通过凭证状态字段 (BSTAT) 值为“V”来识别。event 时间戳为创建日期 (CPUDT)。 捕获 筛选创建时 BKPF-BSTAT = 'V' 的凭证。 事件类型 explicit | |||
| 日记账分录被拒绝 | 审核人或批准人拒绝了会计分录,阻止其过账。凭证通常会退回给创建者进行更正,从而开启返工循环。 | ||
| 为何重要 跟踪拒绝情况是了解流程质量和识别常见错误的关键。高拒绝率表明在数据准确性、政策理解或支持文档不足方面存在问题。 获取方式 此事件是从工作流日志中的状态更改或日记账分录凭证上的自定义状态字段推断出来的。相关状态字段上的更改凭证日志 (CDHDR/CDPOS) 可以提供时间戳。 捕获 通过更改凭证 (CDHDR/CDPOS) 或 workflow 日志识别状态字段更改为“已拒绝”。 事件类型 inferred | |||
| 过账后修改会计分录 | 在会计分录过账到总账后,用户修改其有限的字段集。虽然大多数财务 data 在过账后是不可更改的,但某些字段(如文本或分配)仍可修改。 | ||
| 为何重要 此活动是关键的合规标记。过账后的更改可能暗示有人试图篡改记录,应密切监控以防止欺诈并确保数据完整性。 获取方式 这可以从更改凭证表(CDHDR 和 CDPOS)中可靠地推断出来。CDHDR 中针对该凭证编号且更改日期在过账日期之后的条目,表示发生了过账后更改。 捕获 在 CDHDR 中查找更改时间戳 (UDATE/UTIME) 在凭证过账日期 (BKPF-BUDAT) 之后的记录。 事件类型 inferred | |||
提取指南
步骤
- 前提条件与访问权限:确保您的用户具有查询 SAP S/4HANA 底层数据库或执行 ABAP 报表的必要权限。您需要对 CDS 视图 I_JournalEntry、I_JournalEntryItem 以及表 CDHDR、CDPOS、SRGBREL、SOOD、SWW_WI2OBJ 和 SWWLOGHIST 具有读取权限。通常通过 SAP HANA Studio、DBeaver 等数据库客户端或适用于 Eclipse 的 SAP ABAP 开发工具 (ADT) 进行访问。
- 识别系统特定配置:在运行查询之前,您必须识别会计分录审批 workflow 中使用的具体任务代码。请咨询您的 SAP workflow 管理员,找到对应于提交、拒绝和批准 event 的任务 ID(例如 TS12345678)。这些是填充最终查询中占位符所必需的。
- 准备 SQL 查询:将
query部分提供的完整 SQL 查询复制到您选择的 SQL 客户端或开发工具中。 - 设置查询参数:在查询中找到占位符,并用您的具体数值替换。这包括设置
[YourCompanyCode]、[StartDate]和[EndDate]参数。您还必须将占位符 workflow 任务 ID([Workflow Submitted Task ID]、[Workflow Rejected Task ID]、[Workflow Approved Task ID])替换为您在上一步中识别的值。 - 执行提取查询:针对 SAP S/4HANA 数据库运行修改后的 SQL 查询。根据日期范围和数据量,查询可能需要较长时间。建议在非高峰时段运行。
- 查看初步结果:查询完成后,检查输出的前几行,确保所有列(如 JournalEntryId、ActivityName 和 EventTime)都按预期填充。结果集应包含会计分录生命周期中每个不同业务 event 的一行记录。
- 导出 data 为 CSV:将 SQL 工具中的整个结果集导出为单个 CSV 文件。确保文件使用 UTF-8 编码,以防止特殊字符出现问题。
- 准备上传:在上传到 Process Mining 工具之前,确认 CSV 文件具有所需的标题。data 已经以事件日志的格式结构化,因此不需要进一步的转换或透视。
配置
- 核心数据服务 (CDS) 视图:提取主要使用
I_JournalEntry获取抬头 data,使用I_JournalEntryItem获取行项目和金额详情。这些视图为环球日记账 (ACDOCA) 提供了简化且语义丰富的接口。 - 支持表:为了获取完整的流程视图,查询还关联了多个标准 SAP 表:
CDHDR和CDPOS用于追踪凭证更改。SRGBREL和SOOD用于识别何时通过通用对象服务 (GOS) 链接了附件。SWW_WI2OBJ和SWWLOGHIST用于从审批 workflow 中提取关键 event。
- 日期范围筛选:按特定日期范围筛选 data 对于管理性能至关重要。请在
WHERE子句中使用I_JournalEntry.CreationDateTime字段。建议初次分析时选择 3 到 6 个月的范围。 - 组织筛选:务必按
CompanyCode筛选,以将提取限制在相关的法定实体。在大型系统中一次性查询所有公司代码会导致极长的执行时间。 - Workflow 任务 ID:查询包含 workflow 任务 ID 的占位符(例如
[Workflow Approved Task ID])。这些 ID 在每个 SAP 安装中都是唯一的,必须正确配置才能提取 workflow 活动。如果没有这些 ID,将无法捕获任何提交、批准或拒绝 event。 - 前提条件:执行用户需要对财务、系统和 workflow 表具有广泛的读取权限。这些权限并非标准权限,必须进行专门分配。
a 查询示例 sql
WITH JournalEntryAmountCTE AS (
SELECT
CompanyCode,
AccountingDocument,
FiscalYear,
SUM(AmountInCompanyCodeCurrency) AS AmountInLocalCurrency
FROM I_JournalEntryItem
GROUP BY CompanyCode, AccountingDocument, FiscalYear
),
JournalEntryBaseCTE AS (
SELECT
JE.CompanyCode,
JE.AccountingDocument,
JE.FiscalYear,
JE.CreatedByUser,
JE.CreationDateTime,
JE.PostingDateTime,
JE.PostingDate,
JE.AccountingDocumentType,
JE.DocumentIsParked,
JE.ReversedJournalEntry,
JE.TransactionCode,
JEA.AmountInLocalCurrency
FROM I_JournalEntry AS JE
LEFT JOIN JournalEntryAmountCTE AS JEA
ON JE.CompanyCode = JEA.CompanyCode
AND JE.AccountingDocument = JEA.AccountingDocument
AND JE.FiscalYear = JEA.FiscalYear
WHERE JE.CompanyCode IN ('[YourCompanyCode]')
AND JE.CreationDateTime BETWEEN '[StartDate]' AND '[EndDate]'
)
-- 1. Journal Entry Created
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Created' AS "ActivityName",
BJE.CreationDateTime AS "EventTime",
BJE.CreatedByUser AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
UNION ALL
-- 2. Journal Entry Parked
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Parked' AS "ActivityName",
BJE.CreationDateTime AS "EventTime",
BJE.CreatedByUser AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
WHERE BJE.DocumentIsParked = 'X'
UNION ALL
-- 3. Supporting Documentation Attached
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Supporting Documentation Attached' AS "ActivityName",
TO_TIMESTAMP(SOOD.CREDAT || ' ' || SOOD.CRETIM, 'YYYYMMDD HH24MISS') AS "EventTime",
SOOD.OWNER AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN SRGBREL ON SRGBREL.INSTID_A = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND SRGBREL.TYPEID_A = 'BKPF'
AND SRGBREL.CATID_A = 'BO'
JOIN SOOD ON SOOD.OBJTP = SRGBREL.TYPEID_B
AND SOOD.OBJYR = SRGBREL.INSTID_B(3)
AND SOOD.OBJNO = SRGBREL.INSTID_B(5)
UNION ALL
-- 4. Journal Submitted For Review
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Submitted For Review' AS "ActivityName",
LOG.END_TS AS "EventTime",
LOG.EXEC_USER AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN SWW_WI2OBJ AS WF_LINK ON WF_LINK.INSTID = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND WF_LINK.TYPEID = 'BKPF'
JOIN SWWLOGHIST AS LOG ON LOG.WI_ID = WF_LINK.WI_ID
WHERE LOG.METHOD = '[Workflow Submitted Task ID]'
UNION ALL
-- 5. Journal Entry Rejected
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Rejected' AS "ActivityName",
LOG.END_TS AS "EventTime",
LOG.EXEC_USER AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN SWW_WI2OBJ AS WF_LINK ON WF_LINK.INSTID = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND WF_LINK.TYPEID = 'BKPF'
JOIN SWWLOGHIST AS LOG ON LOG.WI_ID = WF_LINK.WI_ID
WHERE LOG.METHOD = '[Workflow Rejected Task ID]'
UNION ALL
-- 6. Journal Entry Corrected (changed while parked)
SELECT DISTINCT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Corrected' AS "ActivityName",
TO_TIMESTAMP(CH.UDATE || ' ' || CH.UTIME, 'YYYYMMDD HH24MISS') AS "EventTime",
CH.USERNAME AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN CDHDR AS CH ON CH.OBJECTID = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND CH.OBJECTCLASS = 'BELEG'
WHERE BJE.DocumentIsParked = 'X'
UNION ALL
-- 7. Journal Entry Approved
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Approved' AS "ActivityName",
LOG.END_TS AS "EventTime",
LOG.EXEC_USER AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN SWW_WI2OBJ AS WF_LINK ON WF_LINK.INSTID = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND WF_LINK.TYPEID = 'BKPF'
JOIN SWWLOGHIST AS LOG ON LOG.WI_ID = WF_LINK.WI_ID
WHERE LOG.METHOD = '[Workflow Approved Task ID]'
UNION ALL
-- 8. Manual Posting Identified
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Manual Posting Identified' AS "ActivityName",
BJE.PostingDateTime AS "EventTime",
BJE.CreatedByUser AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
WHERE BJE.PostingDateTime IS NOT NULL AND BJE.TransactionCode IN ('FB01', 'F-02', 'FB50', 'FV50', 'FBB1', 'FBV1')
UNION ALL
-- 9. Journal Entry Posted
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Posted' AS "ActivityName",
BJE.PostingDateTime AS "EventTime",
BJE.CreatedByUser AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
WHERE BJE.PostingDateTime IS NOT NULL
UNION ALL
-- 10. Journal Entry Changed After Posting
SELECT DISTINCT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Changed After Posting' AS "ActivityName",
TO_TIMESTAMP(CH.UDATE || ' ' || CH.UTIME, 'YYYYMMDD HH24MISS') AS "EventTime",
CH.USERNAME AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN CDHDR AS CH ON CH.OBJECTID = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND CH.OBJECTCLASS = 'BELEG'
WHERE BJE.PostingDateTime IS NOT NULL AND TO_TIMESTAMP(CH.UDATE || ' ' || CH.UTIME, 'YYYYMMDD HH24MISS') > BJE.PostingDateTime
UNION ALL
-- 11. Journal Entry Cleared
SELECT
JEI.AccountingDocument AS "JournalEntryId",
'Journal Entry Cleared' AS "ActivityName",
MIN(JEI.ClearingDateTime) AS "EventTime",
BJE.CreatedByUser AS "CreatedByUser", -- Note: Clearing user is not directly available here
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM I_JournalEntryItem AS JEI
JOIN JournalEntryBaseCTE AS BJE ON JEI.AccountingDocument = BJE.AccountingDocument
AND JEI.CompanyCode = BJE.CompanyCode
AND JEI.FiscalYear = BJE.FiscalYear
WHERE JEI.ClearingDateTime IS NOT NULL
GROUP BY JEI.AccountingDocument, BJE.CreatedByUser, BJE.CompanyCode, BJE.AccountingDocumentType, BJE.PostingDate, BJE.AmountInLocalCurrency
UNION ALL
-- 12. Journal Entry Reversal Processed
SELECT
OriginalDoc.AccountingDocument AS "JournalEntryId",
'Journal Entry Reversal Processed' AS "ActivityName",
ReversalDoc.PostingDateTime AS "EventTime",
ReversalDoc.CreatedByUser AS "CreatedByUser",
OriginalDoc.CompanyCode AS "CompanyCode",
OriginalDoc.AccountingDocumentType AS "JournalEntryType",
OriginalDoc.PostingDate AS "PostingDate",
OriginalDoc.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS ReversalDoc
JOIN JournalEntryBaseCTE AS OriginalDoc
ON ReversalDoc.ReversedJournalEntry = OriginalDoc.AccountingDocument
AND ReversalDoc.CompanyCode = OriginalDoc.CompanyCode
AND ReversalDoc.ReversalFiscalYear = OriginalDoc.FiscalYear
WHERE ReversalDoc.ReversedJournalEntry IS NOT NULL; 步骤
- 前提条件与访问权限:确保您的用户具有查询 SAP S/4HANA 底层数据库或执行 ABAP 报表的必要权限。您需要对 CDS 视图 I_JournalEntry、I_JournalEntryItem 以及表 CDHDR、CDPOS、SRGBREL、SOOD、SWW_WI2OBJ 和 SWWLOGHIST 具有读取权限。通常通过 SAP HANA Studio、DBeaver 等数据库客户端或适用于 Eclipse 的 SAP ABAP 开发工具 (ADT) 进行访问。
- 识别系统特定配置:在运行查询之前,您必须识别会计分录审批 workflow 中使用的具体任务代码。请咨询您的 SAP workflow 管理员,找到对应于提交、拒绝和批准 event 的任务 ID(例如 TS12345678)。这些是填充最终查询中占位符所必需的。
- 准备 SQL 查询:将
query部分提供的完整 SQL 查询复制到您选择的 SQL 客户端或开发工具中。 - 设置查询参数:在查询中找到占位符,并用您的具体数值替换。这包括设置
[YourCompanyCode]、[StartDate]和[EndDate]参数。您还必须将占位符 workflow 任务 ID([Workflow Submitted Task ID]、[Workflow Rejected Task ID]、[Workflow Approved Task ID])替换为您在上一步中识别的值。 - 执行提取查询:针对 SAP S/4HANA 数据库运行修改后的 SQL 查询。根据日期范围和数据量,查询可能需要较长时间。建议在非高峰时段运行。
- 查看初步结果:查询完成后,检查输出的前几行,确保所有列(如 JournalEntryId、ActivityName 和 EventTime)都按预期填充。结果集应包含会计分录生命周期中每个不同业务 event 的一行记录。
- 导出 data 为 CSV:将 SQL 工具中的整个结果集导出为单个 CSV 文件。确保文件使用 UTF-8 编码,以防止特殊字符出现问题。
- 准备上传:在上传到 Process Mining 工具之前,确认 CSV 文件具有所需的标题。data 已经以事件日志的格式结构化,因此不需要进一步的转换或透视。
配置
- 核心数据服务 (CDS) 视图:提取主要使用
I_JournalEntry获取抬头 data,使用I_JournalEntryItem获取行项目和金额详情。这些视图为环球日记账 (ACDOCA) 提供了简化且语义丰富的接口。 - 支持表:为了获取完整的流程视图,查询还关联了多个标准 SAP 表:
CDHDR和CDPOS用于追踪凭证更改。SRGBREL和SOOD用于识别何时通过通用对象服务 (GOS) 链接了附件。SWW_WI2OBJ和SWWLOGHIST用于从审批 workflow 中提取关键 event。
- 日期范围筛选:按特定日期范围筛选 data 对于管理性能至关重要。请在
WHERE子句中使用I_JournalEntry.CreationDateTime字段。建议初次分析时选择 3 到 6 个月的范围。 - 组织筛选:务必按
CompanyCode筛选,以将提取限制在相关的法定实体。在大型系统中一次性查询所有公司代码会导致极长的执行时间。 - Workflow 任务 ID:查询包含 workflow 任务 ID 的占位符(例如
[Workflow Approved Task ID])。这些 ID 在每个 SAP 安装中都是唯一的,必须正确配置才能提取 workflow 活动。如果没有这些 ID,将无法捕获任何提交、批准或拒绝 event。 - 前提条件:执行用户需要对财务、系统和 workflow 表具有广泛的读取权限。这些权限并非标准权限,必须进行专门分配。
a 查询示例 sql
WITH JournalEntryAmountCTE AS (
SELECT
CompanyCode,
AccountingDocument,
FiscalYear,
SUM(AmountInCompanyCodeCurrency) AS AmountInLocalCurrency
FROM I_JournalEntryItem
GROUP BY CompanyCode, AccountingDocument, FiscalYear
),
JournalEntryBaseCTE AS (
SELECT
JE.CompanyCode,
JE.AccountingDocument,
JE.FiscalYear,
JE.CreatedByUser,
JE.CreationDateTime,
JE.PostingDateTime,
JE.PostingDate,
JE.AccountingDocumentType,
JE.DocumentIsParked,
JE.ReversedJournalEntry,
JE.TransactionCode,
JEA.AmountInLocalCurrency
FROM I_JournalEntry AS JE
LEFT JOIN JournalEntryAmountCTE AS JEA
ON JE.CompanyCode = JEA.CompanyCode
AND JE.AccountingDocument = JEA.AccountingDocument
AND JE.FiscalYear = JEA.FiscalYear
WHERE JE.CompanyCode IN ('[YourCompanyCode]')
AND JE.CreationDateTime BETWEEN '[StartDate]' AND '[EndDate]'
)
-- 1. Journal Entry Created
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Created' AS "ActivityName",
BJE.CreationDateTime AS "EventTime",
BJE.CreatedByUser AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
UNION ALL
-- 2. Journal Entry Parked
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Parked' AS "ActivityName",
BJE.CreationDateTime AS "EventTime",
BJE.CreatedByUser AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
WHERE BJE.DocumentIsParked = 'X'
UNION ALL
-- 3. Supporting Documentation Attached
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Supporting Documentation Attached' AS "ActivityName",
TO_TIMESTAMP(SOOD.CREDAT || ' ' || SOOD.CRETIM, 'YYYYMMDD HH24MISS') AS "EventTime",
SOOD.OWNER AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN SRGBREL ON SRGBREL.INSTID_A = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND SRGBREL.TYPEID_A = 'BKPF'
AND SRGBREL.CATID_A = 'BO'
JOIN SOOD ON SOOD.OBJTP = SRGBREL.TYPEID_B
AND SOOD.OBJYR = SRGBREL.INSTID_B(3)
AND SOOD.OBJNO = SRGBREL.INSTID_B(5)
UNION ALL
-- 4. Journal Submitted For Review
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Submitted For Review' AS "ActivityName",
LOG.END_TS AS "EventTime",
LOG.EXEC_USER AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN SWW_WI2OBJ AS WF_LINK ON WF_LINK.INSTID = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND WF_LINK.TYPEID = 'BKPF'
JOIN SWWLOGHIST AS LOG ON LOG.WI_ID = WF_LINK.WI_ID
WHERE LOG.METHOD = '[Workflow Submitted Task ID]'
UNION ALL
-- 5. Journal Entry Rejected
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Rejected' AS "ActivityName",
LOG.END_TS AS "EventTime",
LOG.EXEC_USER AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN SWW_WI2OBJ AS WF_LINK ON WF_LINK.INSTID = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND WF_LINK.TYPEID = 'BKPF'
JOIN SWWLOGHIST AS LOG ON LOG.WI_ID = WF_LINK.WI_ID
WHERE LOG.METHOD = '[Workflow Rejected Task ID]'
UNION ALL
-- 6. Journal Entry Corrected (changed while parked)
SELECT DISTINCT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Corrected' AS "ActivityName",
TO_TIMESTAMP(CH.UDATE || ' ' || CH.UTIME, 'YYYYMMDD HH24MISS') AS "EventTime",
CH.USERNAME AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN CDHDR AS CH ON CH.OBJECTID = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND CH.OBJECTCLASS = 'BELEG'
WHERE BJE.DocumentIsParked = 'X'
UNION ALL
-- 7. Journal Entry Approved
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Approved' AS "ActivityName",
LOG.END_TS AS "EventTime",
LOG.EXEC_USER AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN SWW_WI2OBJ AS WF_LINK ON WF_LINK.INSTID = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND WF_LINK.TYPEID = 'BKPF'
JOIN SWWLOGHIST AS LOG ON LOG.WI_ID = WF_LINK.WI_ID
WHERE LOG.METHOD = '[Workflow Approved Task ID]'
UNION ALL
-- 8. Manual Posting Identified
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Manual Posting Identified' AS "ActivityName",
BJE.PostingDateTime AS "EventTime",
BJE.CreatedByUser AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
WHERE BJE.PostingDateTime IS NOT NULL AND BJE.TransactionCode IN ('FB01', 'F-02', 'FB50', 'FV50', 'FBB1', 'FBV1')
UNION ALL
-- 9. Journal Entry Posted
SELECT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Posted' AS "ActivityName",
BJE.PostingDateTime AS "EventTime",
BJE.CreatedByUser AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
WHERE BJE.PostingDateTime IS NOT NULL
UNION ALL
-- 10. Journal Entry Changed After Posting
SELECT DISTINCT
BJE.AccountingDocument AS "JournalEntryId",
'Journal Entry Changed After Posting' AS "ActivityName",
TO_TIMESTAMP(CH.UDATE || ' ' || CH.UTIME, 'YYYYMMDD HH24MISS') AS "EventTime",
CH.USERNAME AS "CreatedByUser",
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS BJE
JOIN CDHDR AS CH ON CH.OBJECTID = CONCAT(BJE.CompanyCode, BJE.AccountingDocument, BJE.FiscalYear)
AND CH.OBJECTCLASS = 'BELEG'
WHERE BJE.PostingDateTime IS NOT NULL AND TO_TIMESTAMP(CH.UDATE || ' ' || CH.UTIME, 'YYYYMMDD HH24MISS') > BJE.PostingDateTime
UNION ALL
-- 11. Journal Entry Cleared
SELECT
JEI.AccountingDocument AS "JournalEntryId",
'Journal Entry Cleared' AS "ActivityName",
MIN(JEI.ClearingDateTime) AS "EventTime",
BJE.CreatedByUser AS "CreatedByUser", -- Note: Clearing user is not directly available here
BJE.CompanyCode AS "CompanyCode",
BJE.AccountingDocumentType AS "JournalEntryType",
BJE.PostingDate AS "PostingDate",
BJE.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM I_JournalEntryItem AS JEI
JOIN JournalEntryBaseCTE AS BJE ON JEI.AccountingDocument = BJE.AccountingDocument
AND JEI.CompanyCode = BJE.CompanyCode
AND JEI.FiscalYear = BJE.FiscalYear
WHERE JEI.ClearingDateTime IS NOT NULL
GROUP BY JEI.AccountingDocument, BJE.CreatedByUser, BJE.CompanyCode, BJE.AccountingDocumentType, BJE.PostingDate, BJE.AmountInLocalCurrency
UNION ALL
-- 12. Journal Entry Reversal Processed
SELECT
OriginalDoc.AccountingDocument AS "JournalEntryId",
'Journal Entry Reversal Processed' AS "ActivityName",
ReversalDoc.PostingDateTime AS "EventTime",
ReversalDoc.CreatedByUser AS "CreatedByUser",
OriginalDoc.CompanyCode AS "CompanyCode",
OriginalDoc.AccountingDocumentType AS "JournalEntryType",
OriginalDoc.PostingDate AS "PostingDate",
OriginalDoc.AmountInLocalCurrency AS "AmountInLocalCurrency"
FROM JournalEntryBaseCTE AS ReversalDoc
JOIN JournalEntryBaseCTE AS OriginalDoc
ON ReversalDoc.ReversedJournalEntry = OriginalDoc.AccountingDocument
AND ReversalDoc.CompanyCode = OriginalDoc.CompanyCode
AND ReversalDoc.ReversalFiscalYear = OriginalDoc.FiscalYear
WHERE ReversalDoc.ReversedJournalEntry IS NOT NULL; 步骤
- 创建 ABAP 程序:使用事务代码
SE38进入 ABAP 编辑器。输入新程序名称(例如Z_PM_JE_EXTRACT),点击“创建”。提供合适的标题,将“类型”设置为“可执行程序”,并保存为本地对象或保存在包中。 - 定义选择屏幕:在程序中定义允许用户筛选 data 的参数和选择选项。这应包括会计分录创建日期的范围 (
P_CPUDT_FR,P_CPUDT_TO)、公司代码的选择选项 (SO_BUKRS),以及应用服务器上输出文件的路径 (P_FPATH)。 - 声明数据结构:定义符合所需事件日志格式的内表结构。该结构将保存最终输出。同时,为要选择的 SAP 表(如 BKPF、ACDOCA、CDHDR、CDPOS 以及各种 workflow 表)声明内表和工作区。
- 实现数据选择逻辑:编写核心 ABAP 逻辑以检索 12 个所需活动中每个活动的 data。为每个活动创建单独的子程序 (FORM) 以保持代码整洁。例如,为
get_created_events、get_parked_events、get_workflow_events等创建 FORM。 - 选择“已创建”和“已过账”event:根据用户的选择屏幕标准读取 BKPF 表。BKPF 中的条目表示已创建。状态为
BSTAT = ' '的凭证视为已过账。使用创建时间戳 (CPUDT,CPUTM) 作为 event 时间。 - 选择“预制”event:读取 VBKPF 表(存储预制凭证抬头)。该表中的创建时间戳代表预制 event。
- 选择“Workflow”event(已提交、已批准、已拒绝):查询 workflow 表,如 SWW_WI2OBJ(将会计分录对象链接到 workflow 实例)和 SWWLOGHIST 或 SWWIHEAD(获取特定步骤的详细信息和时间)。您需要识别系统中用于提交、批准和拒绝的特定 workflow 任务 ID。
- 选择“更改”和“更正”event:查询更改凭证表 CDHDR(抬头)和 CDPOS(项目),其中
OBJECTCLAS = 'BELEG'。对于“过账后更改”,筛选更改时间戳在凭证过账日期之后的记录。对于“已更正”,筛选针对预制或被拒绝凭证的更改。 - 选择“冲销”和“已清项”event:通过查找 BKPF 中填充了
STBLG字段(冲销凭证编号)的凭证来识别冲销。冲销 event 时间是冲销凭证的创建时间。通过从 ACDOCA 表中为给定会计分录的行项目选择最新清账日期 (AUGDT) 来识别清账 event。 - 合并并排序 data:在选择每个活动的 data 时,将结果附加到最终的主内表中。所有选择完成后,按
JournalEntryId和EventTime对主表进行排序,以确保每个 case 的时间顺序正确。 - 生成输出文件:使用
OPEN DATASET、LOOP AT... TRANSFER和CLOSE DATASET语句,将排序后的最终内表内容写入 SAP 应用服务器上的指定文件路径。文件应为带有标题行的 CSV 格式。 - 调度执行:对于定期提取,使用事务代码
SM36创建后台作业,按定义的计划(例如每周或每月)运行Z_PM_JE_EXTRACT程序。这将使数据导出过程自动化。
配置
- 日期范围:选择屏幕必须包含会计分录创建日期 (
CPUDT) 的必选日期范围。为确保系统性能,建议分批次提取 data,例如每次提取 3-6 个月的数据。 - 公司代码 (
BUKRS):这是关键的筛选条件,用于将提取范围限制在与 Process Mining 分析相关的特定法定实体内。不建议一次性提取所有公司代码的数据。 - 凭证类型 (
BLART):您可以添加此可选筛选器,以关注特定类型的会计分录,例如用于总账过账的“SA”或用于供应商发票的“KR”。这有助于减少数据量并提高数据集的相关性。 - 文件路径:程序需要 SAP 应用服务器上的逻辑文件路径来写入输出文件。请确保路径有效,且 SAP 系统用户具有该目录的写入权限。使用事务代码
AL11管理和查看服务器目录。 - Workflow 任务 ID:提取 workflow event(已提交、已批准、已拒绝)的逻辑必须配置为您组织会计分录审批 workflow 中使用的特定任务 ID。这些 ID 通常是自定义的,必须由 workflow 顾问或开发人员识别。
- 前提条件:执行程序的开发人员或系统账号需要具有创建和运行 ABAP 程序 (
S_DEVELOP) 的开发权限,以及对财务表 (BKPF, ACDOCA)、更改日志表 (CDHDR, CDPOS) 和 workflow 表 (SWW*) 的广泛读取权限。
a 查询示例 abap
REPORT Z_PM_JE_EXTRACT.
*&---------------------------------------------------------------------*
*&-- Data Structures for Event Log --*
*&---------------------------------------------------------------------*
TYPES: BEGIN OF ty_event_log,
journalentryid TYPE string,
activityname TYPE string,
eventtime TYPE string,
createdbyuser TYPE uname,
companycode TYPE bukrs,
journalentrytype TYPE blart,
postingdate TYPE budat,
amountinlocalcurrency TYPE wrbtr,
END OF ty_event_log.
*&---------------------------------------------------------------------*
*&-- Selection Screen Definition --*
*&---------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001.
PARAMETERS: p_erdat_fr TYPE dats OBLIGATORY DEFAULT sy-datum-30,
p_erdat_to TYPE dats OBLIGATORY DEFAULT sy-datum.
SELECT-OPTIONS: so_bukrs FOR bkpf-bukrs OBLIGATORY.
PARAMETERS: p_fpath TYPE string OBLIGATORY DEFAULT '/usr/sap/trans/tmp/je_event_log.csv'.
SELECTION-SCREEN END OF BLOCK b1.
*&---------------------------------------------------------------------*
*&-- Internal Tables --*
*&---------------------------------------------------------------------*
DATA: gt_event_log TYPE TABLE OF ty_event_log.
*&---------------------------------------------------------------------*
*&-- Main Processing Block --*
*&---------------------------------------------------------------------*
START-OF-SELECTION.
PERFORM get_created_posted_events.
PERFORM get_parked_events.
PERFORM get_attachment_events.
PERFORM get_workflow_events.
PERFORM get_change_events.
PERFORM get_cleared_events.
PERFORM get_reversal_events.
SORT gt_event_log BY journalentryid eventtime.
PERFORM write_output_file.
*&---------------------------------------------------------------------*
*&-- Subroutines for Extracting Individual Activities --*
*&---------------------------------------------------------------------*
FORM get_created_posted_events.
DATA: lt_bkpf TYPE TABLE OF bkpf,
ls_event_log TYPE ty_event_log,
lv_timestamp TYPE string.
SELECT * FROM bkpf INTO TABLE lt_bkpf
WHERE bukrs IN so_bukrs
AND cpudt BETWEEN p_erdat_fr AND p_erdat_to.
LOOP AT lt_bkpf ASSIGNING FIELD-SYMBOL(<fs_bkpf>).
CLEAR ls_event_log.
CONCATENATE <fs_bkpf>-bukrs <fs_bkpf>-belnr <fs_bkpf>-gjahr INTO ls_event_log-journalentryid.
ls_event_log-companycode = <fs_bkpf>-bukrs.
ls_event_log-journalentrytype = <fs_bkpf>-blart.
ls_event_log-postingdate = <fs_bkpf>-budat.
ls_event_log-createdbyuser = <fs_bkpf>-usnam.
" Timestamp format YYYY-MM-DDTHH:MI:SS
CONCATENATE <fs_bkpf>-cpudt(4) '-' <fs_bkpf>-cpudt+4(2) '-' <fs_bkpf>-cpudt+6(2) 'T' <fs_bkpf>-cputm(2) ':' <fs_bkpf>-cputm+2(2) ':' <fs_bkpf>-cputm+4(2) INTO lv_timestamp.
ls_event_log-eventtime = lv_timestamp.
" Activity: Journal Entry Created
ls_event_log-activityname = 'Journal Entry Created'.
SELECT SUM( hsl ) INTO ls_event_log-amountinlocalcurrency FROM acdoca WHERE belnr = <fs_bkpf>-belnr AND gjahr = <fs_bkpf>-gjahr AND bukrs = <fs_bkpf>-bukrs.
APPEND ls_event_log TO gt_event_log.
" Activity: Journal Entry Posted (if not parked)
IF <fs_bkpf>-bstat = ' '.
ls_event_log-activityname = 'Journal Entry Posted'.
APPEND ls_event_log TO gt_event_log.
" Activity: Manual Posting Identified (based on T-Code)
CASE <fs_bkpf>-tcode.
WHEN 'FB01' OR 'F-02' OR 'FB50' OR 'F-22' OR 'F-43'.
ls_event_log-activityname = 'Manual Posting Identified'.
APPEND ls_event_log TO gt_event_log.
ENDCASE.
ENDIF.
ENDLOOP.
ENDFORM.
FORM get_parked_events.
DATA: ls_event_log TYPE ty_event_log, lv_timestamp TYPE string.
SELECT * FROM vbkpf
WHERE bukrs IN so_bukrs
AND cpudt BETWEEN p_erdat_fr AND p_erdat_to.
CLEAR ls_event_log.
CONCATENATE vbkpf-bukrs vbkpf-belnr vbkpf-gjahr INTO ls_event_log-journalentryid.
CONCATENATE vbkpf-cpudt(4) '-' vbkpf-cpudt+4(2) '-' vbkpf-cpudt+6(2) 'T' vbkpf-cputm(2) ':' vbkpf-cputm+2(2) ':' vbkpf-cputm+4(2) INTO lv_timestamp.
ls_event_log-activityname = 'Journal Entry Parked'.
ls_event_log-eventtime = lv_timestamp.
ls_event_log-createdbyuser = vbkpf-usnam.
ls_event_log-companycode = vbkpf-bukrs.
ls_event_log-journalentrytype = vbkpf-blart.
ls_event_log-postingdate = vbkpf-budat.
APPEND ls_event_log TO gt_event_log.
ENDSELECT.
ENDFORM.
FORM get_attachment_events.
DATA: lt_bdocs TYPE TABLE OF srgbtbrel, ls_event_log TYPE ty_event_log, lv_timestamp TYPE string.
SELECT * FROM srgbtbrel INTO TABLE lt_bdocs
WHERE typeid_a = 'BUS2081' " Object type for Accounting Document
AND catid_a = 'BO'.
LOOP AT lt_bdocs ASSIGNING FIELD-SYMBOL(<fs_bdocs>).
CHECK <fs_bdocs>-instid_a(4) IN so_bukrs.
DATA(lv_bukrs) = <fs_bdocs>-instid_a(4).
DATA(lv_belnr) = <fs_bdocs>-instid_a+4(10).
DATA(lv_gjahr) = <fs_bdocs>-instid_a+14(4).
SELECT SINGLE cpudt, cputm, usnam, blart, budat FROM bkpf
INTO (DATA(lv_cpudt), DATA(lv_cputm), DATA(lv_usnam), DATA(lv_blart), DATA(lv_budat))
WHERE bukrs = lv_bukrs AND belnr = lv_belnr AND gjahr = lv_gjahr.
IF sy-subrc = 0 AND lv_cpudt BETWEEN p_erdat_fr AND p_erdat_to.
CLEAR ls_event_log.
CONCATENATE lv_bukrs lv_belnr lv_gjahr INTO ls_event_log-journalentryid.
" Note: Using document creation time as a proxy for attachment time.
CONCATENATE lv_cpudt(4) '-' lv_cpudt+4(2) '-' lv_cpudt+6(2) 'T' lv_cputm(2) ':' lv_cputm+2(2) ':' lv_cputm+4(2) INTO lv_timestamp.
ls_event_log-activityname = 'Supporting Documentation Attached'.
ls_event_log-eventtime = lv_timestamp.
ls_event_log-createdbyuser = lv_usnam.
ls_event_log-companycode = lv_bukrs.
ls_event_log-journalentrytype = lv_blart.
ls_event_log-postingdate = lv_budat.
APPEND ls_event_log TO gt_event_log.
ENDIF.
ENDLOOP.
ENDFORM.
FORM get_workflow_events.
" This is a simplified example. Real workflow logic can be complex.
" You must identify your specific Task IDs for these events.
DATA: ls_event_log TYPE ty_event_log, lv_timestamp TYPE string.
DATA: BEGIN OF ls_wi, wi_id TYPE sww_wiid, cr_date TYPE sww_cd, cr_time TYPE sww_ct, task TYPE sww_task, instid TYPE swo_typeid, END OF ls_wi.
SELECT h~wi_id h~cr_date h~cr_time h~wi_rh_task o~instid
FROM swwwihead AS h
JOIN sww_wi2obj AS o ON h~wi_id = o~wi_id
INTO @ls_wi
WHERE o~typeid = 'BUS2081' AND o~catid = 'BO'
AND h~cr_date BETWEEN @p_erdat_fr AND @p_erdat_to.
DATA(lv_bukrs) = ls_wi-instid(4).
DATA(lv_belnr) = ls_wi-instid+4(10).
DATA(lv_gjahr) = ls_wi-instid+14(4).
IF lv_bukrs IN so_bukrs.
CLEAR ls_event_log.
CONCATENATE lv_bukrs lv_belnr lv_gjahr INTO ls_event_log-journalentryid.
CONCATENATE ls_wi-cr_date(4) '-' ls_wi-cr_date+4(2) '-' ls_wi-cr_date+6(2) 'T' ls_wi-cr_time(2) ':' ls_wi-cr_time+2(2) ':' ls_wi-cr_time+4(2) INTO lv_timestamp.
ls_event_log-eventtime = lv_timestamp.
ls_event_log-companycode = lv_bukrs.
CASE ls_wi-task.
WHEN '[Your Submit Task ID]'. " e.g., TS20000139
ls_event_log-activityname = 'Journal Submitted For Review'.
APPEND ls_event_log TO gt_event_log.
WHEN '[Your Approve Task ID]'. " e.g., TS20000142
ls_event_log-activityname = 'Journal Entry Approved'.
APPEND ls_event_log TO gt_event_log.
WHEN '[Your Reject Task ID]'. " e.g., TS20000141
ls_event_log-activityname = 'Journal Entry Rejected'.
APPEND ls_event_log TO gt_event_log.
ENDCASE.
ENDIF.
ENDSELECT.
ENDFORM.
FORM get_change_events.
DATA: lt_cdhdr TYPE TABLE OF cdhdr, ls_event_log TYPE ty_event_log, lv_timestamp TYPE string.
SELECT * FROM cdhdr INTO TABLE lt_cdhdr
WHERE objectclas = 'BELEG'
AND udate BETWEEN p_erdat_fr AND p_erdat_to.
LOOP AT lt_cdhdr ASSIGNING FIELD-SYMBOL(<fs_cdhdr>).
DATA(lv_bukrs) = <fs_cdhdr>-objectid(4).
DATA(lv_belnr) = <fs_cdhdr>-objectid+4(10).
DATA(lv_gjahr) = <fs_cdhdr>-objectid+14(4).
IF lv_bukrs IN so_bukrs.
SELECT SINGLE bstat, budat, blart FROM bkpf
INTO (DATA(lv_bstat), DATA(lv_budat), DATA(lv_blart))
WHERE bukrs = lv_bukrs AND belnr = lv_belnr AND gjahr = lv_gjahr.
IF sy-subrc = 0.
CLEAR ls_event_log.
CONCATENATE lv_bukrs lv_belnr lv_gjahr INTO ls_event_log-journalentryid.
CONCATENATE <fs_cdhdr>-udate(4) '-' <fs_cdhdr>-udate+4(2) '-' <fs_cdhdr>-udate+6(2) 'T' <fs_cdhdr>-utime(2) ':' <fs_cdhdr>-utime+2(2) ':' <fs_cdhdr>-utime+4(2) INTO lv_timestamp.
ls_event_log-eventtime = lv_timestamp.
ls_event_log-createdbyuser = <fs_cdhdr>-username.
ls_event_log-companycode = lv_bukrs.
ls_event_log-journalentrytype = lv_blart.
ls_event_log-postingdate = lv_budat.
IF lv_bstat = ' ' AND <fs_cdhdr>-udate > lv_budat.
ls_event_log-activityname = 'Journal Entry Changed After Posting'.
APPEND ls_event_log TO gt_event_log.
ELSEIF lv_bstat <> ' '.
ls_event_log-activityname = 'Journal Entry Corrected'.
APPEND ls_event_log TO gt_event_log.
ENDIF.
ENDIF.
ENDIF.
ENDLOOP.
ENDFORM.
FORM get_cleared_events.
DATA: ls_event_log TYPE ty_event_log, lv_timestamp TYPE string.
DATA: BEGIN OF ls_clear, belnr TYPE belnr_d, gjahr TYPE gjahr, bukrs TYPE bukrs, augdt TYPE augdt, END OF ls_clear, lt_clear LIKE TABLE OF ls_clear.
SELECT belnr, gjahr, bukrs, MAX( augdt ) AS augdt FROM acdoca
INTO TABLE @lt_clear
WHERE bukrs IN @so_bukrs
AND augdt NE '00000000'
AND augdt BETWEEN @p_erdat_fr AND @p_erdat_to
GROUP BY belnr, gjahr, bukrs.
LOOP AT lt_clear INTO ls_clear.
SELECT SINGLE usnam, blart, budat FROM bkpf
INTO (DATA(lv_usnam), DATA(lv_blart), DATA(lv_budat))
WHERE bukrs = ls_clear-bukrs AND belnr = ls_clear-belnr AND gjahr = ls_clear-gjahr.
IF sy-subrc = 0.
CLEAR ls_event_log.
CONCATENATE ls_clear-bukrs ls_clear-belnr ls_clear-gjahr INTO ls_event_log-journalentryid.
CONCATENATE ls_clear-augdt(4) '-' ls_clear-augdt+4(2) '-' ls_clear-augdt+6(2) 'T12:00:00' INTO lv_timestamp. " Clearing date has no time, use midday
ls_event_log-activityname = 'Journal Entry Cleared'.
ls_event_log-eventtime = lv_timestamp.
ls_event_log-createdbyuser = lv_usnam.
ls_event_log-companycode = ls_clear-bukrs.
ls_event_log-journalentrytype = lv_blart.
ls_event_log-postingdate = lv_budat.
APPEND ls_event_log TO gt_event_log.
ENDIF.
ENDLOOP.
ENDFORM.
FORM get_reversal_events.
DATA: lt_reversals TYPE TABLE OF bkpf, ls_event_log TYPE ty_event_log, lv_timestamp TYPE string.
SELECT * FROM bkpf INTO TABLE lt_reversals
WHERE bukrs IN so_bukrs
AND cpudt BETWEEN p_erdat_fr AND p_erdat_to
AND stblg IS NOT NULL.
LOOP AT lt_reversals ASSIGNING FIELD-SYMBOL(<fs_rev>).
SELECT SINGLE usnam, blart, budat FROM bkpf
INTO (DATA(lv_usnam), DATA(lv_blart), DATA(lv_budat))
WHERE bukrs = <fs_rev>-bukrs AND belnr = <fs_rev>-stblg AND gjahr = <fs_rev>-gjahr.
IF sy-subrc = 0.
CLEAR ls_event_log.
CONCATENATE <fs_rev>-bukrs <fs_rev>-stblg <fs_rev>-gjahr INTO ls_event_log-journalentryid.
CONCATENATE <fs_rev>-cpudt(4) '-' <fs_rev>-cpudt+4(2) '-' <fs_rev>-cpudt+6(2) 'T' <fs_rev>-cputm(2) ':' <fs_rev>-cputm+2(2) ':' <fs_rev>-cputm+4(2) INTO lv_timestamp.
ls_event_log-activityname = 'Journal Entry Reversal Processed'.
ls_event_log-eventtime = lv_timestamp.
ls_event_log-createdbyuser = lv_usnam.
ls_event_log-companycode = <fs_rev>-bukrs.
ls_event_log-journalentrytype = lv_blart.
ls_event_log-postingdate = lv_budat.
APPEND ls_event_log TO gt_event_log.
ENDIF.
ENDLOOP.
ENDFORM.
FORM write_output_file.
DATA: lv_line TYPE string.
FIELD-SYMBOLS: <fs_event_log> TYPE ty_event_log.
OPEN DATASET p_fpath FOR OUTPUT IN TEXT MODE ENCODING UTF-8.
IF sy-subrc NE 0.
MESSAGE 'Error opening file.' TYPE 'E'.
RETURN.
ENDIF.
" Write Header
lv_line = 'JournalEntryId,ActivityName,EventTime,CreatedByUser,CompanyCode,JournalEntryType,PostingDate,AmountInLocalCurrency'.
TRANSFER lv_line TO p_fpath.
LOOP AT gt_event_log ASSIGNING <fs_event_log>.
CONCATENATE <fs_event_log>-journalentryid <fs_event_log>-activityname <fs_event_log>-eventtime <fs_event_log>-createdbyuser <fs_event_log>-companycode <fs_event_log>-journalentrytype <fs_event_log>-postingdate <fs_event_log>-amountinlocalcurrency
INTO lv_line SEPARATED BY ','.
TRANSFER lv_line TO p_fpath.
ENDLOOP.
CLOSE DATASET p_fpath.
WRITE: / 'File successfully written to', p_fpath.
ENDFORM.