您的记录到报告流程:日记账分录数据模板
您的记录到报告流程:日记账分录数据模板
- 全面分析建议纳入的属性
- 需要跟踪的日记账分录关键活动
- SAP ECC数据提取实用指南
记录到报告流程:日记账分录属性
| 名称 | 说明 | ||
|---|---|---|---|
| 事件时间 EventTime | 表示日记账分录中某项活动或事件发生时间的时间戳。 | ||
| 说明 事件时间记录日记账分录流程中每项活动的确切日期和时间。计算周期时间、处理时长和步骤间延误等所有时间指标,都离不开这些数据。时间戳的来源因活动而异,可能是凭证创建日期和时间(CPUDT/CPUTM),也可能是日志中的更改时间戳(CDHDR-UDATE/UTIME)。 在分析中,事件时间用于按时间顺序排列事件,是构建流程图的基础。它对于计算所有与时间相关的KPI至关重要,例如日记账分录平均周期时间、平均审批时间以及审批至过账的时间。 重要性 此时间戳是所有时间相关分析的基础,可用于计算周期时间、处理时长并识别瓶颈。 获取途径 根据活动从不同字段获取,主要包括BKPF中的创建时间戳(CPUDT、CPUTM)或CDHDR中的更改凭证时间戳(UDATE、UTIME)。 示例 2023-10-26T09:00:00Z2023-10-26T14:30:15Z2023-10-27T11:05:00Z | |||
| 日记账分录ID JournalEntryId | 用于唯一标识财务会计凭证,由公司代码、凭证编号和会计年度组成。 | ||
| 说明 日记账分录ID是跟踪日记账分录生命周期的主要案例标识符。它是一个复合键,通常由公司代码(BUKRS)、凭证编号(BELNR)和会计年度(GJAHR)拼接而成,确保其在整个SAP系统中唯一。 在流程分析中,此ID用于关联创建、暂存、提交、审批、驳回和过账等相关活动。通过跟踪此标识符,您可以还原每笔日记账分录的端到端流程,衡量周期时间,并识别特定分录的流程偏差或瓶颈。 重要性 这是跟踪日记账分录从创建到最终过账的关键标识,可用于端到端流程分析和流程变体对比。 获取途径 此属性通常由BKPF表中的多个字段拼接而成:公司代码(BUKRS)、凭证编号(BELNR)和会计年度(GJAHR)。 示例 1000-1000000123-20232000-1900000456-20231000-1800000789-2024 | |||
| 活动名称 ActivityName | 日记账分录流程中某个时间点发生的业务活动或事件名称。 | ||
| 说明 活动名称用于描述日记账分录生命周期中的具体步骤,例如“日记账分录已创建”“日记账分录已批准”或“日记账分录已过账”。此属性通常根据SAP中的多个数据源得出,包括交易代码(TCODE)、更改凭证日志(表CDHDR和CDPOS)以及凭证状态字段。 活动分析是流程挖掘的核心。它可以将流程图可视化、计算步骤之间的转换时间,并识别返工循环(例如“日记账分录已驳回”后接“日记账分录已更正”)。这些数据是周期时间、返工率和流程变体相关仪表板的基础。 重要性 此属性定义流程图中的步骤,使日记账分录工作流能够可视化、分析和优化。 获取途径 数据来自多个来源,包括BKPF中的交易代码(TCODE)、凭证状态、SWW_WI2OBJ等表中的工作流日志,以及CDHDR和CDPOS中的更改凭证。 示例 日记账分录已创建日记账分录已批准日记账分录已驳回日记账分录已过账 | |||
| 上次数据更新 LastDataUpdate | 表示上次从源系统提取或刷新数据时间的时间戳。 | ||
| 说明 此属性记录最近一次从SAP ECC提取数据的日期和时间。它是一个元数据字段,对于了解所分析数据的新鲜度和时效性至关重要。 在任何流程挖掘仪表板或分析中,了解上次更新时间对于建立用户对数据的信任并支持有依据的决策都很重要。它有助于回答“这些信息更新到什么时候?”这一问题。 重要性 让用户了解数据的时效性,明确分析所覆盖的时间范围,帮助用户信任分析结果。 获取途径 此元数据字段由数据提取工具或ETL流程在数据刷新时生成并保存。 示例 2024-05-20T04:00:00Z2024-05-21T04:00:00Z2024-05-22T04:00:00Z | |||
| 源系统 SourceSystem | 提取流程数据的系统。 | ||
| 说明 此属性标识数据来源,在此处指具体的SAP ECC实例。通常在数据提取过程中添加一个静态值。 在包含多个ERP系统或数据源的环境中,此属性虽然简单,却十分重要。它能清晰呈现数据沿袭,并支持按来源系统筛选或细分分析。 重要性 清晰呈现数据沿袭,对于跟踪数据质量至关重要,尤其是在存在多个源系统的环境中。 获取途径 通常在数据转换过程中添加一个静态值,用于标识具体的SAP ECC实例(例如'ECC_PROD_100')。 示例 SAP ECC EHP8ECC_FIN_PRODSAP_ERP_60 | |||
| 交易代码 TransactionCode | 用于创建或处理日记账分录的SAP交易代码。 | ||
| 说明 交易代码(T-Code)是SAP中特定功能或程序的唯一标识符。对于日记账分录,它表示分录的创建方式,例如手工创建(FB01、F-02)、暂存(FV50)或通过自动接口创建。 此属性对于“手工活动优化”仪表板十分重要。通过分析T-Code,可以区分手工活动和自动活动,找出最耗时的手工流程,并发现自动化机会,从而减少手工工作、提升效率。 重要性 帮助区分手工流程和自动化流程,识别自动化和流程标准化的机会。 获取途径 位于凭证抬头表BKPF的TCODE字段。 示例 FB01F-02FV50FBD1 | |||
| 公司代码 CompanyCode | 代表独立法人实体的组织单位,需以此为单位编制财务报表。 | ||
| 说明 公司代码是SAP财务管理中的基本组织单位,代表一个独立法人实体,也是日记账分录凭证抬头中的关键字段。 此属性对于按法人实体细分流程分析至关重要。它支持比较组织不同部分的流程绩效、合规率和KPI结果。例如,可用于判断审批延误或冲销率偏高是否集中在某些公司代码。 重要性 支持按组织内不同法人实体或业务单元筛选和比较流程绩效。 获取途径 位于凭证抬头表BKPF的BUKRS字段。 示例 10002000US01DE01 | |||
| 凭证类型 DocumentType | 用于对会计凭证分类,并控制其处理和存储方式。 | ||
| 说明 凭证类型用于区分不同类型的业务交易,例如总账分录(SA)、供应商发票(KR)或资产过账(AA)。它在系统配置期间定义,并分配给每笔日记账分录。 此属性对于分析至关重要,可按交易性质细分流程。“按类型统计日记账分录处理量”仪表板和“按日记账分录类型统计平均周期时间”KPI都直接使用此字段。它有助于发现某些类型的分录是否更容易出现延误、返工或冲销。 重要性 支持按交易类型细分分析,帮助识别流程问题是否集中在某些类型的日记账分录中。 获取途径 位于凭证抬头表BKPF的BLART字段。 示例 SAKRREAA | |||
| 是否已冲销 IsReversed | 用于标记日记账分录是否已冲销的布尔值。 | ||
| 说明 此标记用于识别后来被另一张会计凭证冲销的日记账分录。在SAP中,被冲销的凭证会关联到冲销凭证,形成清晰、可追溯的审计记录。 此属性是“日记账分录冲销分析”仪表板和“日记账分录冲销率”KPI的基础。您可以据此筛选已冲销的分录,调查数据录入错误或会计处理不当等根本原因,从而减少冲销发生次数。 重要性 标记后来被撤销的分录,直接支持冲销分析,帮助您查明错误根因并提升数据完整性。 获取途径 根据BKPF表中的冲销凭证编号字段(STBLG)得出。若STBLG不为空,则此标记为true。 示例 truefalse | |||
| 用户 User | 创建或更改日记账分录的人员的SAP用户ID。 | ||
| 说明 此属性记录负责某项活动(如创建、暂存或过账凭证)的SAP用户名。数据直接来自凭证抬头表或更改日志表。 分析用户属性有助于了解团队和个人的绩效。通过跟踪每位用户的活动量和处理时间,它为用户生产力仪表板提供数据支持。此属性还有助于识别参与返工循环、冲销或合规偏差的人员,从而有针对性地开展培训或流程改进。 重要性 识别每项活动的负责人,支持分析用户绩效、工作量分配和返工模式。 获取途径 通常来自BKPF表(创建者字段USNAM)或CDHDR表(更改者字段USERNAME)。 示例 ABROWNCJONESDSMITH | |||
| 过账日期 PostingDate | 交易记入总账并影响财务期间的日期。 | ||
| 说明 过账日期决定日记账分录计入哪个会计期间。从财务和合规角度看,这是一个关键日期字段,必须符合会计期间结账安排和相关法规。 在流程挖掘中,此日期用于监控合规情况。“合规遵循监控”仪表板和“合规一致性率”KPI会使用此属性,检查分录是否在正确期间内过账。它还可用于分析日记账分录数量随时间变化的趋势。 重要性 对于财务报告和合规分析至关重要,可确保分录在正确的会计期间过账。 获取途径 位于凭证抬头表BKPF的BUDAT字段。 示例 2023-10-312023-11-302024-01-15 | |||
| 冲销原因 ReversalReason | 用于说明日记账分录被冲销原因的代码。 | ||
| 说明 冲销凭证时,SAP允许用户指定原因代码。此代码以结构化方式记录冲销原因,例如过账日期错误或数据录入错误。 此属性是“日记账分录冲销分析”仪表板的重要输入。分析最常见的冲销原因,有助于组织发现流程中的系统性问题或培训缺口,并采取有针对性的措施,避免后续错误、降低冲销率。 重要性 直接揭示冲销发生的原因,帮助您有针对性地分析根因,减少后续错误。 获取途径 位于凭证抬头表BKPF的STGRD字段。 示例 010205 | |||
| 凭证总金额 TotalDocumentAmount | 日记账分录按凭证币种计价的总金额。 | ||
| 说明 此属性表示日记账分录的财务总额,通常通过汇总该凭证所有借方或贷方行项目的绝对值计算得出。 按金额分析流程有助于发现重要规律。例如,高金额分录可能需要遵循不同且更严格的审批路径。您可以用此属性筛选或细分分析,了解周期时间、驳回率或审批延误是否与分录金额相关。 重要性 支持分析财务影响,例如日记账分录金额与处理时间或驳回率之间的关系。 获取途径 此字段通过汇总BSEG表中指定日记账分录所有行项目的金额字段(WRBTR或DMBTR)计算得出。 示例 1500.0025000.75125.50 | |||
| 审批时间 ApprovalTime | 从日记账分录提交审批,到获批或被拒绝所经过的时间。 | ||
| 说明 此指标衡量审批子流程的耗时,而审批往往是整体周期时间的重要组成部分。计算方式为“日记账分录已提交”活动与对应的“日记账分录已批准”或“日记账分录已拒绝”活动之间的时间差。 审批时间是“日记账分录审批表现”仪表板和“日记账分录平均审批时间”KPI的核心指标。分析审批耗时有助于您发现审批工作流中的瓶颈、衡量审批人的表现,并为调整审批阈值等流程变更提供依据。 重要性 量化审批阶段的耗时,帮助您定位并解决审核与审批工作流中的延误。 获取途径 用“日记账分录已批准”或“日记账分录已拒绝”事件的时间戳,减去“日记账分录已提交”事件的时间戳。 示例 P1DT2HPT4H15MP3D | |||
| 成本中心 CostCenter | 控制范围内的组织单位,代表发生成本的地点。 | ||
| 说明 成本中心是管理会计(CO)模块中的关键主数据,通常分配到日记账分录的行项目,用于跟踪特定部门、职能或地点的成本。 纳入成本中心后,您可以更细致地分析日记账分录流程,例如判断哪些部门返工较多、周期时间较长,或手工录入量较大,从而按部门了解流程效率。 重要性 按部门或职能领域分析流程表现,帮助您定位局部效率问题。 获取途径 位于凭证行项目表BSEG的KOSTL字段。 示例 4100CC_FINANCE_US10010101 | |||
| 是否暂存 IsParked | 布尔标记,表示日记账分录过账前是否曾保存为暂存凭证。 | ||
| 说明 暂存凭证可让用户保存尚未完成的日记账分录,而不影响财务余额。之后,其他用户可以补全或审核该凭证,再进行过账。此标记用于识别经过暂存环节的分录。 分析此属性有助于了解暂存功能的使用情况。您可以判断暂存是否被用作非正式审核环节,并识别由此造成的延误。此属性也支持端到端周期时间分析,区分直接过账的分录和先暂存的分录。 重要性 识别使用暂存功能的分录。暂存可能造成延误,也可能表明存在非正式审核流程。 获取途径 根据BKPF表中的凭证状态字段(BSTAT)得出。值为“V”表示暂存凭证。 示例 truefalse | |||
| 是否返工 IsRework | 布尔标记,表示日记账分录是否经历返工循环,例如被拒绝后再更正。 | ||
| 说明 此标记用于识别偏离“理想路径”并需要纠正的案例。对于某笔日记账分录,如果观察到“日记账分录已拒绝”后接“日记账分录已更正”等活动序列,通常会将此标记设为true。 此属性是计算“日记账分录返工率”KPI以及分析“返工与拒绝率”仪表板的关键依据。它有助于量化流程低效程度,并为调查返工根因提供依据,例如要求不明确或文档不足。 重要性 标记需要更正的分录,帮助您量化返工、分析根因并提高一次做对的比例。 获取途径 这是一个计算属性,根据案例的活动顺序得出。出现拒绝或更正活动时,即识别为返工循环。 示例 truefalse | |||
| 货币代码 CurrencyKey | 日记账分录中记录金额所使用的货币代码。 | ||
| 说明 此属性表示日记账分录使用的货币,例如USD、EUR或JPY,为凭证中的金额提供必要背景信息。 货币通常不是主要分析维度,但正确解读金额离不开这一信息。在跨国组织中,您也可以按货币细分分析,比较外币和本币分录对应的流程是否存在差异。 重要性 为所有金额提供必要背景信息,确保财务分析和解读准确。 获取途径 位于凭证抬头表BKPF的WAERS字段。 示例 USDEURGBPJPY | |||
记录到报告流程:日记账分录活动
| 活动 | 说明 | ||
|---|---|---|---|
| 日记账分录冲销已处理 | 此活动表示冲销之前已过账的日记账分录。冲销会生成一张新的会计凭证,用于抵消原分录。 | ||
| 重要性 这是衡量数据质量和流程准确性的关键事件。冲销率偏高说明初始数据录入或审批阶段可能存在系统性问题,而且每次冲销都意味着返工。 获取途径 此事件可从原凭证抬头(BKPF表)中识别。凭证被冲销时,SAP会填入冲销凭证编号(BKPF-STBLG)和冲销原因(BKPF-STGRD)。事件时间戳为新冲销凭证的过账日期。 采集 识别原凭证中的BKPF-STBLG何时填入数据;时间戳为冲销凭证的过账日期。 事件类型 explicit | |||
| 日记账分录已批准 | 此活动标志着工作流中的日记账分录已获最终批准,符合过账条件。工作流日志会在最后的“放行”或“批准”步骤完成时记录此事件。 | ||
| 重要性 这是审批流程结束的关键节点。审批完成前的时长是衡量审批效率的重要KPI,而此事件至过账之间的时间则反映审批后的延迟。 获取途径 根据SAP业务工作流日志中最终审批步骤的完成时间戳推断。这是凭证过账或准备过账之前的最后一项审批操作。 采集 在工作流日志中识别最后的“放行”或“批准”步骤是否已完成。 事件类型 inferred | |||
| 日记账分录已提交 | 此活动表示暂存的日记账分录已由创建者完成,现可进入审核和审批环节。通常通过启动与暂存凭证关联的SAP业务工作流任务来记录此活动。 | ||
| 重要性 此活动标志着分录从创建者移交给审批人,并开始计算审批周期时间KPI。它是衡量审批工作流效率的重要节点。 获取途径 根据与财务凭证对象关联的审批工作流实例的开始时间推断。需要分析SWW_WI2OBJ等工作流日志表,查找为特定公司代码、凭证编号和会计年度启动的工作流。 采集 识别暂存凭证对象对应的工作流开始事件。 事件类型 inferred | |||
| 日记账分录已过账 | 此活动表示日记账分录已正式记入总账,并影响财务报表。当凭证状态设为“已过账”并分配过账日期时,系统会明确记录此事件。 | ||
| 重要性 这是最重要的节点,标志着日记账分录已成功处理。端到端周期时间通常计算至此节点,也是财务结账分析的关键事件。 获取途径 当BKPF表中的凭证具有过账日期(BKPF-BUDAT)时,即可识别此事件。对于暂存凭证,此事件对应状态BKPF-BSTAT从'V'变为空的时刻。过账时间戳为录入日期BKPF-CPUDT。 采集 识别BKPF-BSTAT从'V'变为空的时刻;对于直接过账,则识别凭证创建事件。 事件类型 explicit | |||
| 日记账分录暂存 | 此活动表示日记账分录已初步创建,但尚未正式过账至总账。在SAP中,用户通过暂存交易保存凭证并将其状态设为“已暂存”时,系统会明确记录此活动。 | ||
| 重要性 这是涉及审核和审批流程的关键开始事件。分析暂存至过账之间的时间,有助于发现过账前处理和审批阶段的延误。 获取途径 此事件从凭证抬头表BKPF中识别。凭证创建时若状态为BKPF-BSTAT = 'V',则视为已暂存。事件时间戳为创建日期和时间,即BKPF-CPUDT和BKPF-CPUTM。 采集 在BKPF中查找BKPF-BSTAT为'V'的凭证创建记录。 事件类型 explicit | |||
| 已识别手工录入 | 此活动用于识别日记账分录是通过在线手工交易创建,还是通过自动接口或批处理创建。它不是用户操作,而是根据系统数据计算得出的分录属性。 | ||
| 重要性 区分手工分录和自动分录是有针对性地改进流程的关键。手工流程通常是标准化和自动化举措的重点。 获取途径 通过分析凭证抬头表BKPF中的字段计算此属性。'FB01'、'FB50'或'FV50'等交易代码(BKPF-TCODE)表示手工录入;其他T代码或特定批量输入名称(BKPF-AWKEY)则可能表示自动处理。 采集 根据BKPF-TCODE或凭证抬头中的其他源系统标识进行判断。 事件类型 calculated | |||
| 已识别跨公司过账 | 此计算所得的活动用于标记影响多个公司代码的日记账分录。通过分析单张财务凭证的行项目即可确定。 | ||
| 重要性 跨公司交易的处理和审批要求可能更复杂。识别此类交易后,可以单独分析其周期时间和流程路径,找出特有的瓶颈。 获取途径 检查指定凭证编号(BELNR)对应的行项目表BSEG。如果行项目中包含多个不同的公司代码(BSEG-BUKRS),则该分录属于跨公司过账。 采集 检查单个BKPF-BELNR是否对应多个不同的BSEG-BUKRS值。 事件类型 calculated | |||
| 已附加文档 | 此活动表示用户将发票或电子表格等支持文件附加到日记账分录。标准会计事件日志不会明确记录此事件,通常需要检查与会计凭证对象关联的附件是否已创建,以此推断。 | ||
| 重要性 跟踪此活动有助于确认是否遵循了文档留存政策。附件延迟可能是审批周期过长的根本原因。 获取途径 此事件难以可靠地捕获为带时间戳的事件。可以分析通用对象服务(GOS)附件表(如SOOD),并将附件创建时间戳与日记账分录对象键关联,以推断此事件。 采集 根据GOS表(如SOOD)中关联对象的创建时间戳进行推断。 事件类型 inferred | |||
| 日记账分录已创建 | 表示日记账分录已直接创建并过账,之前没有暂存步骤。用户通过SAP直接过账交易创建凭证时,系统会记录此活动。 | ||
| 重要性 对于无需审批工作流的简单日记账分录,此活动可作为另一种起始点。它有助于区分简单的直接过账和较复杂的暂存分录。 获取途径 此事件对应BKPF表中的凭证创建记录,凭证状态BKPF-BSTAT为空(已过账)。事件时间戳为创建日期BKPF-CPUDT。对于此类凭证,“已创建”和“已过账”事件同时发生。 采集 在BKPF中查找BKPF-BSTAT为空的凭证创建记录。 事件类型 explicit | |||
| 日记账分录已更正 | 此活动表示原创建者在日记账分录被退回修改后,对暂存分录进行了修改。可通过检测“需要修改”事件之后对凭证所做的更改来推断。 | ||
| 重要性 跟踪更正情况有助于量化返工所耗费的精力。从提出修改要求到完成更正之间的时间,可以反映解决已提交分录问题所需的时间。 获取途径 分析暂存凭证的更改凭证日志表CDHDR和CDPOS即可推断此事件。工作流驳回后记录的更改表示已完成更正。时间戳取自CDHDR表。 采集 在驳回事件之后,识别CDHDR/CDPOS中的更改日志记录。 事件类型 inferred | |||
| 日记账分录已驳回 | 此活动表示日记账分录被最终驳回,之后不会再过账。在审批工作流中,这通常是终止状态,暂存凭证随后会被删除。 | ||
| 重要性 跟踪驳回情况对质量管理至关重要。分析驳回原因和频率,有助于提高日记账分录首次做对的比例。 获取途径 此结果记录在SAP业务工作流日志中,表示最终的“驳回”用户决策已终止流程。暂存凭证之后可能会被删除。 采集 在凭证的工作流日志中识别终止状态“驳回”。 事件类型 inferred | |||
| 日记账分录行项目已清账 | 此活动表示对按未清项目管理的总账科目行项目(如银行清算科目)进行核销。将某个行项目与另一个项目匹配并结清时,就会发生此活动。 | ||
| 重要性 对于银行对账等流程,项目清账所需时间是重要的KPI。此活动有助于分析对账和月末结账流程的效率。 获取途径 此事件从行项目表BSEG中获取。行项目清账后,清账日期(BSEG-AUGDT)和清账凭证(BSEG-AUGBL)字段会填入数据。事件时间戳为清账日期。 采集 识别行项目的清账日期(BSEG-AUGDT)何时填入数据。 事件类型 explicit | |||
| 日记账分录需要修改 | 表示审批人在工作流中审核日记账分录后,将其退回创建者修改。此事件可从工作流日志中识别,日志会记录“驳回”或“退回”等用户决策。 | ||
| 重要性 此活动对于识别返工循环至关重要。返工循环是造成效率低下和流程偏差的主要原因之一。此事件频繁发生,通常说明分录质量有问题或要求不明确。 获取途径 此事件根据SAP业务工作流日志中特定用户决策步骤的时间戳推断,该步骤对应“驳回”或“退回修改”操作。 采集 在工作流日志中识别“驳回”或“返工”决策的时间戳。 事件类型 inferred | |||
| 暂存的日记账分录已删除 | 表示删除了从未过账的暂存日记账分录。分录被驳回或创建有误时,可能会发生此情况。 | ||
| 重要性 此活动标志着流程未能成功完成。分析暂存凭证被删除的原因,有助于发现重复分录或对流程理解不清等问题。 获取途径 当BKPF表中暂存凭证的状态发生更改时,系统会记录此事件。状态字段BKPF-BSTAT会更新为'Z'(暂存凭证已删除)。更改时间戳可从凭证更改日志CDHDR中获取。 采集 识别BKPF-BSTAT更新为'Z'的时刻。 事件类型 explicit | |||
数据提取指南
步骤
- 创建ABAP程序:在SAP系统中,进入事务代码SE38(ABAP编辑器)。为新程序输入名称,例如Z_PM_JE_EXTRACTION,然后点击创建。填写合适的标题,并将程序类型设为“可执行程序”。
- 定义选择屏幕:在程序源代码中定义选择屏幕,供用户指定凭证创建日期范围、公司代码和凭证类型等参数,以限制提取的数据量。
- 声明数据结构:定义一个内部表结构,用于存放最终事件日志数据。该结构必须包含所有必需字段:JournalEntryId、ActivityName、EventTime、SourceSystem、LastDataUpdate,以及User、CompanyCode和PostingDate等建议属性。
- 编写数据选择逻辑:编写核心ABAP SQL查询,提取14项必需活动的数据。需要从BKPF(抬头)和BSEG(行项目)等主表、CDHDR和CDPOS等更改日志表、SWWLOGHIST等工作流表,以及BSAS和BSAK等清账表中选择数据。
- 提取暂存和已过账凭证:对于“日记账分录已暂存”事件,从BKPF中选择凭证状态(BSTAT)为“V”的记录。对于“日记账分录已创建”和“日记账分录已过账”事件,从BKPF中选择状态为空的记录,表示普通的已过账凭证。
- 提取更改和删除事件:查询更改凭证表CDHDR和CDPOS,筛选对象类别“BELEG”。根据凭证键筛选出对应“日记账分录已更正”或“暂存日记账分录已删除”活动的更改记录。
- 提取工作流事件:如需捕获“日记账分录已提交”“已批准”“已拒绝”和“已请求更改”等活动,请查询工作流表。使用SWW_WI2OBJ将会计凭证关联到工作流实例,再从SWWLOGHIST读取具体的用户决策或状态变更。
- 识别计算得出的事件:对于“已识别手工录入”,将事务代码(BKPF-TCODE)与已知手工录入事务代码列表进行比对。对于“已识别跨公司过账”,分析指定凭证的BSEG行项目,检查是否涉及多个公司代码。
- 汇总并转换数据:选择各项活动的数据后,将其转换为最终事件日志结构。拼接公司代码、凭证编号和会计年度,生成JournalEntryId。将SAP日期和时间合并为EventTime时间戳,并将各查询结果追加到最终内部表中。
- 实现文件导出:使用ABAP文件处理语句(如OPEN DATASET、LOOP AT、TRANSFER和CLOSE DATASET),将汇总后的内部表写入SAP应用服务器目录中的CSV或纯文本文件(可通过事务代码AL11查看)。
- 设置后台作业:进入事务代码SM36(定义后台作业)。创建新作业,添加执行ABAP程序的步骤,并设置计划,例如在非高峰时段每晚或每周运行,以自动执行数据提取。
- 获取并整理文件:使用事务代码CG3Y,或联系系统管理员,将生成的文件从应用服务器下载到本地计算机。请确认文件编码和格式适合上传到流程挖掘工具。
配置
- 日期范围:务必设置日期范围以控制运行性能。优先使用凭证创建日期(BKPF-CPUDT)作为筛选条件。首次分析建议选择3至6个月的数据。测试时,可选取已知包含数据的几天进行验证。
- 公司代码筛选:始终按公司代码(BKPF-BUKRS)筛选。一次提取所有公司代码的数据会大量占用系统资源。建议先从一个或少数几个相关公司代码开始。
- 凭证类型筛选:使用凭证类型筛选条件(BKPF-BLART)缩小范围。例如,如果无需分析所有凭证类型,可仅选择“SA”(总账凭证)。
- 工作流任务ID:提取工作流事件的逻辑取决于系统中用于审批、拒绝和提交的特定任务ID。请根据公司的工作流定义,在程序源代码中配置这些ID。
- 性能注意事项:程序会联接多个大型表,尤其是CDPOS和工作流历史表。在业务高峰时段运行可能影响系统性能。请始终将其设为后台作业,在非高峰时段运行。如果性能问题反复出现,可考虑创建辅助数据库索引。
- 前提条件:执行此方法的用户需要具备ABAP开发授权(用于SE38)以及创建和管理后台作业的权限(用于SM36)。用户或作业还需要对所有相关财务、工作流和系统表(BKPF、BSEG、CDHDR、CDPOS、SWWLOGHIST等)具有读取权限。
查询示例 abap
REPORT Z_PM_JE_EXTRACTION.
*&---------------------------------------------------------------------*
*& Data Structures for Final Event Log
*&---------------------------------------------------------------------*
TYPES: BEGIN OF ty_event_log,
journalentryid TYPE string,
activityname TYPE string,
eventtime TYPE timestamp,
sourcesystem TYPE string,
lastdataupdate TYPE timestamp,
username TYPE uname,
companycode TYPE bukrs,
documenttype TYPE blart,
postingdate TYPE budat,
transactioncode TYPE tcode,
isreversed TYPE abap_bool,
END OF ty_event_log.
DATA: lt_final_log TYPE STANDARD TABLE OF ty_event_log.
DATA: ls_event TYPE ty_event_log.
*&---------------------------------------------------------------------*
*& Selection Screen Parameters
*&---------------------------------------------------------------------*
SELECT-OPTIONS: s_bukrs FOR bkpf-bukrs OBLIGATORY,
s_blart FOR bkpf-blart,
s_cpudt FOR bkpf-cpudt OBLIGATORY.
PARAMETERS: p_sysid TYPE sy-sysid DEFAULT sy-sysid.
*&---------------------------------------------------------------------*
*& Main Logic
*&---------------------------------------------------------------------*
START-OF-SELECTION.
DATA(lv_last_update) = cl_abap_context_info=>get_system_timestamp( ).
" 1. Journal Entry Parked
SELECT CONCAT( a~bukrs, a~belnr, a~gjahr ) AS journalentryid,
'Journal Entry Parked' AS activityname,
a~cpudt, a~cputm,
a~usnam AS username,
a~bukrs AS companycode,
a~blart AS documenttype,
a~bldat AS postingdate,
a~tcode AS transactioncode
FROM bkpf AS a
WHERE a~bukrs IN s_bukrs
AND a~blart IN s_blart
AND a~cpudt IN s_cpudt
AND a~bstat = 'V' " Parked Document
INTO TABLE @DATA(lt_parked).
IF sy-subrc = 0.
LOOP AT lt_parked ASSIGNING FIELD-SYMBOL(<fs_parked>).
ls_event-journalentryid = <fs_parked>-journalentryid.
ls_event-activityname = <fs_parked>-activityname.
CONVERT DATE <fs_parked>-cpudt TIME <fs_parked>-cputm INTO TIME STAMP ls_event-eventtime TIME ZONE sy-zonlo.
ls_event-sourcesystem = p_sysid.
ls_event-lastdataupdate = lv_last_update.
ls_event-username = <fs_parked>-username.
ls_event-companycode = <fs_parked>-companycode.
ls_event-documenttype = <fs_parked>-documenttype.
ls_event-postingdate = <fs_parked>-postingdate.
ls_event-transactioncode = <fs_parked>-transactioncode.
APPEND ls_event TO lt_final_log.
ENDLOOP.
ENDIF.
" 2. Journal Entry Created (directly posted, not parked first)
" 9. Journal Entry Posted
" These two events happen at the same time for a direct posting.
SELECT CONCAT( bukrs, belnr, gjahr ) AS journalentryid,
cpudt, cputm, usnam, bukrs, blart, budat, tcode, stblg
FROM bkpf
WHERE bukrs IN s_bukrs
AND blart IN s_blart
AND cpudt IN s_cpudt
AND bstat = '' " Normal, posted document
INTO TABLE @DATA(lt_posted).
IF sy-subrc = 0.
LOOP AT lt_posted ASSIGNING FIELD-SYMBOL(<fs_posted>).
" Activity: Journal Entry Created
ls_event-journalentryid = <fs_posted>-journalentryid.
ls_event-activityname = 'Journal Entry Created'.
CONVERT DATE <fs_posted>-cpudt TIME <fs_posted>-cputm INTO TIME STAMP ls_event-eventtime TIME ZONE sy-zonlo.
ls_event-sourcesystem = p_sysid.
ls_event-lastdataupdate = lv_last_update.
ls_event-username = <fs_posted>-usnam.
ls_event-companycode = <fs_posted>-bukrs.
ls_event-documenttype = <fs_posted>-blart.
ls_event-postingdate = <fs_posted>-budat.
ls_event-transactioncode = <fs_posted>-tcode.
ls_event-isreversed = COND #( WHEN <fs_posted>-stblg IS NOT INITIAL THEN abap_true ELSE abap_false ).
APPEND ls_event TO lt_final_log.
" Activity: Journal Entry Posted
ls_event-activityname = 'Journal Entry Posted'.
APPEND ls_event TO lt_final_log.
ENDLOOP.
ENDIF.
" 3. Documentation Attached (via GOS)
SELECT a~instid_a, c~cr_timestamp
FROM srgbtbrel AS a
INNER JOIN sood AS b ON a~instid_b = b~objid
INNER JOIN socf AS c ON b~filid = c~filid
WHERE a~typeid_a = 'BKPF'
AND a~bukrs IN s_bukrs
INTO TABLE @DATA(lt_attachments).
IF sy-subrc = 0.
LOOP AT lt_attachments ASSIGNING FIELD-SYMBOL(<fs_attach>).
ls_event-journalentryid = |{ <fs_attach>-instid_a(4) }{ <fs_attach>-instid_a+4(10) }{ <fs_attach>-instid_a+14(4) }|.
ls_event-activityname = 'Documentation Attached'.
ls_event-eventtime = <fs_attach>-cr_timestamp.
" Other attributes may need to be looked up from BKPF if needed.
APPEND ls_event TO lt_final_log.
ENDLOOP.
ENDIF.
" 4, 5, 6, 7, 8: Workflow events (Submitted, Changes Requested, Corrected, Approved, Rejected)
" This is a simplified example. Real logic depends on specific workflow templates.
SELECT a~instid, b~wi_cd, b~wi_ct, b~wi_aagent, b~wi_text
FROM sww_wi2obj AS a
INNER JOIN swwloghist AS b ON a~wi_id = b~wi_id
WHERE a~typeid = 'BKPF'
AND a~catid = 'BO'
AND a~bukrs IN s_bukrs
AND b~wi_cd BETWEEN s_cpudt-low AND s_cpudt-high
INTO TABLE @DATA(lt_workflow).
IF sy-subrc = 0.
LOOP AT lt_workflow ASSIGNING FIELD-SYMBOL(<fs_wf>).
ls_event-journalentryid = |{ <fs_wf>-instid(4) }{ <fs_wf>-instid+4(10) }{ <fs_wf>-instid+14(4) }|.
ls_event-activityname = CASE <fs_wf>-wi_text. " Simplified logic based on work item text
WHEN '[Placeholder for Submit Text]' THEN 'Journal Entry Submitted'
WHEN '[Placeholder for Approve Text]' THEN 'Journal Entry Approved'
WHEN '[Placeholder for Reject Text]' THEN 'Journal Entry Rejected'
WHEN '[Placeholder for Rework Text]' THEN 'Journal Entry Changes Requested'
ELSE ''
ENDCASE.
IF ls_event-activityname IS NOT INITIAL.
CONVERT DATE <fs_wf>-wi_cd TIME <fs_wf>-wi_ct INTO TIME STAMP ls_event-eventtime TIME ZONE sy-zonlo.
ls_event-username = <fs_wf>-wi_aagent.
APPEND ls_event TO lt_final_log.
ENDIF.
ENDLOOP.
ENDIF.
" 10. Manual Entry Identified & 11. Cross-Company Posting Identified
SELECT bukrs, belnr, gjahr, tcode FROM bkpf
WHERE bukrs IN s_bukrs AND blart IN s_blart AND cpudt IN s_cpudt
INTO TABLE @DATA(lt_calc_base).
LOOP AT lt_calc_base ASSIGNING FIELD-SYMBOL(<fs_calc>).
ls_event-journalentryid = |{ <fs_calc>-bukrs }{ <fs_calc>-belnr }{ <fs_calc>-gjahr }|.
" Check for manual entry T-Codes
IF <fs_calc>-tcode = 'FB01' OR <fs_calc>-tcode = 'F-02' OR <fs_calc>-tcode = 'FB50'.
ls_event-activityname = 'Manual Entry Identified'.
APPEND ls_event TO lt_final_log.
ENDIF.
" Check for cross-company posting
SELECT SINGLE bukrs FROM bseg WHERE belnr = <fs_calc>-belnr AND gjahr = <fs_calc>-gjahr AND bukrs <> <fs_calc>-bukrs INTO @DATA(lv_cross_bukrs).
IF sy-subrc = 0.
ls_event-activityname = 'Cross-Company Posting Identified'.
APPEND ls_event TO lt_final_log.
ENDIF.
ENDLOOP.
" 12. Journal Entry Line Item Cleared
SELECT a~bukrs, a~belnr, a~gjahr, a~augdt, a~augbl
FROM bsas AS a " G/L Cleared Items
WHERE a~bukrs IN s_bukrs
AND a~budat IN s_cpudt
INTO TABLE @DATA(lt_cleared_gl).
IF sy-subrc = 0.
LOOP AT lt_cleared_gl ASSIGNING FIELD-SYMBOL(<fs_clr>).
ls_event-journalentryid = |{ <fs_clr>-bukrs }{ <fs_clr>-belnr }{ <fs_clr>-gjahr }|.
ls_event-activityname = 'Journal Entry Line Item Cleared'.
CONVERT DATE <fs_clr>-augdt INTO TIME STAMP ls_event-eventtime TIME ZONE sy-zonlo.
" User is often not directly available for clearing events
APPEND ls_event TO lt_final_log.
ENDLOOP.
ENDIF.
" 13. Parked Journal Entry Deleted & 6. Journal Entry Corrected
SELECT objectid, changenr, username, udate, utime FROM cdhdr
WHERE objectclas = 'BELEG'
AND udate IN s_cpudt
INTO TABLE @DATA(lt_cdhdr).
LOOP AT lt_cdhdr ASSIGNING FIELD-SYMBOL(<fs_cdhdr>).
SELECT SINGLE tcode FROM cdpos WHERE changenr = <fs_cdhdr>-changenr AND fname = 'BSTAT' AND value_new = 'Z' INTO @DATA(lv_deleted_tcode).
ls_event-journalentryid = |{ <fs_cdhdr>-objectid(4) }{ <fs_cdhdr>-objectid+4(10) }{ <fs_cdhdr>-objectid+14(4) }|.
CONVERT DATE <fs_cdhdr>-udate TIME <fs_cdhdr>-utime INTO TIME STAMP ls_event-eventtime TIME ZONE sy-zonlo.
ls_event-username = <fs_cdhdr>-username.
IF sy-subrc = 0.
ls_event-activityname = 'Parked Journal Entry Deleted'.
APPEND ls_event TO lt_final_log.
ELSE.
ls_event-activityname = 'Journal Entry Corrected'.
APPEND ls_event TO lt_final_log.
ENDIF.
ENDLOOP.
" 14. Journal Entry Reversal Processed
SELECT CONCAT( a~bukrs, a~belnr, a~gjahr ) AS journalentryid,
a~cpudt, a~cputm, a~usnam
FROM bkpf AS a
WHERE a~bukrs IN s_bukrs
AND a~blart IN s_blart
AND a~cpudt IN s_cpudt
AND a~stblg IS NOT NULL " Document is a reversal
INTO TABLE @DATA(lt_reversals).
IF sy-subrc = 0.
LOOP AT lt_reversals ASSIGNING FIELD-SYMBOL(<fs_rev>).
ls_event-journalentryid = <fs_rev>-journalentryid.
ls_event-activityname = 'Journal Entry Reversal Processed'.
CONVERT DATE <fs_rev>-cpudt TIME <fs_rev>-cputm INTO TIME STAMP ls_event-eventtime TIME ZONE sy-zonlo.
ls_event-username = <fs_rev>-usnam.
APPEND ls_event TO lt_final_log.
ENDLOOP.
ENDIF.
" Final step: Output to file
DATA(lv_filename) = |/tmp/je_extraction_{ sy-datum }_{ sy-uzeit }.csv|.
OPEN DATASET lv_filename FOR OUTPUT IN TEXT MODE ENCODING UTF-8.
IF sy-subrc = 0.
" Write header
DATA(lv_header) = 'JournalEntryId,ActivityName,EventTime,SourceSystem,LastDataUpdate,User,CompanyCode,DocumentType,PostingDate,TransactionCode,IsReversed'.
TRANSFER lv_header TO lv_filename.
LOOP AT lt_final_log INTO ls_event.
DATA(lv_line) = |"{ ls_event-journalentryid }","|
|{ ls_event-activityname }","|
|{ ls_event-eventtime }","|
|{ ls_event-sourcesystem }","|
|{ ls_event-lastdataupdate }","|
|{ ls_event-username }","|
|{ ls_event-companycode }","|
|{ ls_event-documenttype }","|
|{ ls_event-postingdate }","|
|{ ls_event-transactioncode }","|
|{ ls_event-isreversed }"|.
TRANSFER lv_line TO lv_filename.
ENDLOOP.
CLOSE DATASET lv_filename.
ENDIF. 步骤
- 建立数据库连接:获取SAP ECC数据库的只读凭据。使用标准SQL客户端(如DBeaver、SAP HANA Studio或SQL Server Management Studio)连接数据库。
- 准备SQL查询:将本文档“query”部分提供的完整SQL查询复制到SQL客户端。
- 设置提取参数:执行前,配置查询中的占位符。将“[START_DATE]”和“[END_DATE]”替换为所需日期范围,格式为“YYYYMMDD”。将“[COMPANY_CODE_1]”和“[COMPANY_CODE_2]”替换为您要分析的SAP公司代码。
- 指定源系统:在主
SELECT语句中,将占位符“[Your SAP System ID]”替换为实际的SAP系统ID(SID),以正确标识数据来源。 - 执行查询:在SAP数据库上运行配置好的SQL查询。执行时间取决于日期范围和数据库表的大小。
- 检查初步结果:查询完成后,快速检查返回的行,确认数据已按预期填充。检查是否包含多种活动,以及
JournalEntryId和EventTime等关键字段是否非空。 - 处理时间戳:查询会将日期和时间字段拼接为
YYYYMMDDHHMMSS字符串。请确认后续处理流程或目标系统能够解析此格式;如果数据库支持,也可调整SQLCONCAT函数,改用YYYY-MM-DDTHH:MI:SS等ISO 8601格式。 - 导出数据:通过SQL客户端将完整结果集导出为CSV文件。请使用UTF-8编码,避免特殊字符出现问题。
- 准备上传:上传到流程挖掘工具前,请确认列标题符合所需数据结构。
JournalEntryId、ActivityName和EventTime是关键字段。添加LastDataUpdate列,并填入执行数据提取时的时间戳。 - 最终验证:按照“validationSteps”部分列出的步骤,确认提取的数据完整、准确后再开始分析。
配置
- 数据库授权:数据库用户需要具备以下SAP表的读取权限:BKPF、BSEG、CDHDR、CDPOS、T001和V_USERNAME。对于工作流相关活动,还需要访问SWW_WI2OBJ和SWWLOGHIST。此类权限通常仅授予专业技术团队。
- 日期范围筛选:务必按特定日期范围筛选数据,以确保查询性能。提供的查询使用开始和结束日期占位符,筛选凭证创建日期(
BKPF.CPUDT)。首次分析建议选择3至6个月的数据。 - 实体筛选:为控制数据量并聚焦分析,请始终按公司代码(
BKPF.BUKRS)筛选。您也可以按凭证类型(BKPF.BLART)筛选,只纳入相关日记账分录类型,例如“SA”(总账凭证);如果发票或付款等运营凭证不在分析范围内,可将其排除。 - 性能注意事项:直接查询BSEG和CDPOS等核心表会大量占用系统资源。强烈建议在业务非高峰时段执行提取,以免影响终端用户的系统性能。单次提取的数据范围不要超过一年。
- 工作流任务ID:查询中包含
'[WF_TASK_ID_SUBMIT]'和'[WF_TASK_ID_APPROVE]'等占位符。请将其替换为系统中日记账分录工作流配置所使用的实际任务ID。您可以咨询SAP工作流专家,或在事务代码PFTC中查看技术工作流定义以确定这些ID。
查询示例 sql
WITH DOC_HEADERS AS (
SELECT
BUKRS,
BELNR,
GJAHR,
BLART,
BLDAT,
BUDAT,
CPUDT,
CPUTM,
USNAM,
TCODE,
BSTAT,
STBLG,
XRECH
FROM BKPF
WHERE CPUDT BETWEEN '[START_DATE]' AND '[END_DATE]'
AND BUKRS IN ('[COMPANY_CODE_1]', '[COMPANY_CODE_2]')
)
-- Event 1: Journal Entry Created (Directly Posted)
SELECT
CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AS "JournalEntryId",
'Journal Entry Created' AS "ActivityName",
TO_TIMESTAMP(CONCAT(H.CPUDT, H.CPUTM), 'YYYYMMDDHH24MISS') AS "EventTime",
U.NAME_TEXT AS "User",
H.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
H.TCODE AS "TransactionCode",
CASE WHEN H.STBLG IS NOT NULL AND H.STBLG <> '' THEN TRUE ELSE FALSE END AS "IsReversed"
FROM DOC_HEADERS H
LEFT JOIN V_USERNAME U ON H.USNAM = U.BNAME
WHERE H.BSTAT = '' OR H.BSTAT = 'U'
UNION ALL
-- Event 2: Journal Entry Parked
SELECT
CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AS "JournalEntryId",
'Journal Entry Parked' AS "ActivityName",
TO_TIMESTAMP(CONCAT(H.CPUDT, H.CPUTM), 'YYYYMMDDHH24MISS') AS "EventTime",
U.NAME_TEXT AS "User",
H.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
H.TCODE AS "TransactionCode",
FALSE AS "IsReversed"
FROM DOC_HEADERS H
LEFT JOIN V_USERNAME U ON H.USNAM = U.BNAME
WHERE H.BSTAT = 'V'
UNION ALL
-- Event 3: Journal Entry Posted (from Parked state)
SELECT
CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AS "JournalEntryId",
'Journal Entry Posted' AS "ActivityName",
TO_TIMESTAMP(CONCAT(C.UDATE, C.UTIME), 'YYYYMMDDHH24MISS') AS "EventTime",
U.NAME_TEXT AS "User",
H.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
C.TCODE AS "TransactionCode",
CASE WHEN H.STBLG IS NOT NULL AND H.STBLG <> '' THEN TRUE ELSE FALSE END AS "IsReversed"
FROM DOC_HEADERS H
JOIN CDHDR C ON C.OBJECTCLAS = 'BELEG' AND C.OBJECTID = CONCAT(H.BUKRS, H.BELNR, H.GJAHR)
JOIN CDPOS P ON C.CHANGENR = P.CHANGENR AND P.OBJECTCLAS = 'BELEG' AND P.OBJECTID = C.OBJECTID
LEFT JOIN V_USERNAME U ON C.USERNAME = U.BNAME
WHERE H.BSTAT <> 'V'
AND P.TABNAME = 'BKPF'
AND P.FNAME = 'BSTAT'
AND P.VALUE_OLD = 'V'
AND P.VALUE_NEW <> 'V'
UNION ALL
-- Event 4: Parked Journal Entry Deleted
SELECT
CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AS "JournalEntryId",
'Parked Journal Entry Deleted' AS "ActivityName",
TO_TIMESTAMP(CONCAT(C.UDATE, C.UTIME), 'YYYYMMDDHH24MISS') AS "EventTime",
U.NAME_TEXT AS "User",
H.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
C.TCODE AS "TransactionCode",
FALSE AS "IsReversed"
FROM DOC_HEADERS H
JOIN CDHDR C ON C.OBJECTCLAS = 'BELEG' AND C.OBJECTID = CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AND C.TCODE = 'FBV0'
JOIN CDPOS P ON C.CHANGENR = P.CHANGENR AND P.OBJECTCLAS = 'BELEG' AND P.OBJECTID = C.OBJECTID
LEFT JOIN V_USERNAME U ON C.USERNAME = U.BNAME
WHERE P.TABNAME = 'BKPF'
AND P.FNAME = 'BSTAT'
AND P.VALUE_OLD = 'V'
AND P.VALUE_NEW = 'Z'
UNION ALL
-- Event 5: Journal Entry Reversal Processed
SELECT
CONCAT(H.BUKRS, H.STBLG, H.GJAHR) AS "JournalEntryId", -- Linking to the original document
'Journal Entry Reversal Processed' AS "ActivityName",
TO_TIMESTAMP(CONCAT(H.CPUDT, H.CPUTM), 'YYYYMMDDHH24MISS') AS "EventTime",
U.NAME_TEXT AS "User",
H.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
H.TCODE AS "TransactionCode",
TRUE AS "IsReversed"
FROM DOC_HEADERS H
LEFT JOIN V_USERNAME U ON H.USNAM = U.BNAME
WHERE H.STBLG IS NOT NULL AND H.STBLG <> ''
UNION ALL
-- Event 6: Journal Entry Line Item Cleared
SELECT
CONCAT(B.BUKRS, B.BELNR, B.GJAHR) AS "JournalEntryId",
'Journal Entry Line Item Cleared' AS "ActivityName",
TO_TIMESTAMP(B.AUGDT, 'YYYYMMDD') AS "EventTime", -- Clearing date used as event time
U.NAME_TEXT AS "User",
B.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
NULL AS "TransactionCode", -- Clearing transaction is in the clearing document header, complex to retrieve here
CASE WHEN H.STBLG IS NOT NULL AND H.STBLG <> '' THEN TRUE ELSE FALSE END AS "IsReversed"
FROM BSEG B
JOIN DOC_HEADERS H ON B.BUKRS = H.BUKRS AND B.BELNR = H.BELNR AND B.GJAHR = H.GJAHR
LEFT JOIN V_USERNAME U ON H.USNAM = U.BNAME
WHERE B.AUGBL IS NOT NULL AND B.AUGBL <> '' AND B.AUGDT <> '00000000'
UNION ALL
-- Event 7: Journal Entry Corrected (changes to a parked document)
SELECT
CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AS "JournalEntryId",
'Journal Entry Corrected' AS "ActivityName",
TO_TIMESTAMP(CONCAT(C.UDATE, C.UTIME), 'YYYYMMDDHH24MISS') AS "EventTime",
U.NAME_TEXT AS "User",
H.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
C.TCODE AS "TransactionCode",
FALSE AS "IsReversed"
FROM DOC_HEADERS H
JOIN CDHDR C ON C.OBJECTCLAS = 'BELEG' AND C.OBJECTID = CONCAT(H.BUKRS, H.BELNR, H.GJAHR)
LEFT JOIN V_USERNAME U ON C.USERNAME = U.BNAME
WHERE H.BSTAT = 'V' AND C.TCODE IN ('FBV2', 'FBV4') -- FBV2 is change parked doc, FBV4 is change parked doc header
UNION ALL
-- Event 8: Documentation Attached (inferred from GOS attachment creation, requires configuration)
SELECT
CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AS "JournalEntryId",
'Documentation Attached' AS "ActivityName",
TO_TIMESTAMP(CONCAT(REL.RECDATE, '000000'), 'YYYYMMDDHH24MISS') AS "EventTime",
U.NAME_TEXT AS "User",
H.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
H.TCODE AS "TransactionCode",
FALSE AS "IsReversed"
FROM DOC_HEADERS H
JOIN SRGBTBREL REL ON REL.INSTID_A = CONCAT('BUS2081', H.BUKRS, H.BELNR, H.GJAHR) -- BUS2081 is object type for BKPF
LEFT JOIN V_USERNAME U ON REL.RECUNAM = U.BNAME
WHERE REL.TYPEID_A = 'BUS2081' AND REL.RELTYPE = 'ATTA'
UNION ALL
-- Events 9-13 from Workflow (Submitted, Changes Requested, Approved, Rejected) requires specific workflow config
-- This is a generic template. The WI_RH_TASK must be adapted to your system.
SELECT
CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AS "JournalEntryId",
CASE
WHEN LOG.WI_RH_TASK = '[WF_TASK_ID_SUBMIT]' THEN 'Journal Entry Submitted'
WHEN LOG.WI_RH_TASK = '[WF_TASK_ID_APPROVE]' AND LOG.METHOD = 'DECISION' AND LOG.EVT_ID = 'COMPLETED' THEN 'Journal Entry Approved'
WHEN LOG.WI_RH_TASK = '[WF_TASK_ID_REJECT]' AND LOG.METHOD = 'DECISION' AND LOG.EVT_ID = 'COMPLETED' THEN 'Journal Entry Rejected'
WHEN LOG.WI_RH_TASK = '[WF_TASK_ID_CHANGES_REQ]' AND LOG.METHOD = 'DECISION' AND LOG.EVT_ID = 'COMPLETED' THEN 'Journal Entry Changes Requested'
ELSE NULL
END AS "ActivityName",
TO_TIMESTAMP(CONCAT(LOG.EVT_DATE, LOG.EVT_TIME), 'YYYYMMDDHH24MISS') AS "EventTime",
U.NAME_TEXT AS "User",
H.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
NULL AS "TransactionCode",
FALSE AS "IsReversed"
FROM DOC_HEADERS H
JOIN SWW_WI2OBJ WIOBJ ON WIOBJ.INSTID = CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AND WIOBJ.TYPEID = 'BKPF'
JOIN SWWLOGHIST LOG ON WIOBJ.WI_ID = LOG.WI_ID
LEFT JOIN V_USERNAME U ON LOG.EXEC_USER = U.BNAME
WHERE LOG.WI_RH_TASK IN ('[WF_TASK_ID_SUBMIT]', '[WF_TASK_ID_APPROVE]', '[WF_TASK_ID_REJECT]', '[WF_TASK_ID_CHANGES_REQ]')
UNION ALL
-- Event 14: Manual Entry Identified
SELECT
CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AS "JournalEntryId",
'Manual Entry Identified' AS "ActivityName",
TO_TIMESTAMP(CONCAT(H.CPUDT, H.CPUTM), 'YYYYMMDDHH24MISS') AS "EventTime",
U.NAME_TEXT AS "User",
H.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
H.TCODE AS "TransactionCode",
CASE WHEN H.STBLG IS NOT NULL AND H.STBLG <> '' THEN TRUE ELSE FALSE END AS "IsReversed"
FROM DOC_HEADERS H
LEFT JOIN V_USERNAME U ON H.USNAM = U.BNAME
WHERE H.TCODE IN ('FB01', 'F-02', 'FB50', 'F-04', 'F-22', 'F-43', 'FB60', 'FB70', 'FV50', 'FV60', 'FV70')
UNION ALL
-- Event 15: Cross-Company Posting Identified
SELECT
CONCAT(H.BUKRS, H.BELNR, H.GJAHR) AS "JournalEntryId",
'Cross-Company Posting Identified' AS "ActivityName",
TO_TIMESTAMP(CONCAT(H.CPUDT, H.CPUTM), 'YYYYMMDDHH24MISS') AS "EventTime",
U.NAME_TEXT AS "User",
H.BUKRS AS "CompanyCode",
H.BLART AS "DocumentType",
H.BUDAT AS "PostingDate",
H.TCODE AS "TransactionCode",
CASE WHEN H.STBLG IS NOT NULL AND H.STBLG <> '' THEN TRUE ELSE FALSE END AS "IsReversed"
FROM DOC_HEADERS H
LEFT JOIN V_USERNAME U ON H.USNAM = U.BNAME
WHERE H.XRECH = 'X' 步骤
- 建立 SAP 连接:在第三方 ETL 工具中,为您的 SAP ECC 系统配置新的源连接。通常需要提供应用服务器详细信息、客户端、系统编号,以及拥有必要 RFC 授权的专用 SAP 用户。
- 定义数据源:在提取项目中,将所需的 SAP 表添加为数据源。主要包括 BKPF(会计凭证抬头)、BSEG(会计凭证明细)、VBSEGK(暂存凭证抬头)、CDHDR(更改凭证抬头)、CDPOS(更改凭证明细)、SWW_WI2OBJ(工作流与对象的关联)、SWWLOGHIST(工作流日志)和 SRGBTBREL(GOS 附件关系)。
- 提取基础事件(已创建和已暂存):创建第一个数据流,提取初始事件。对于“日记账分录已暂存”,使用 VBSEGK 作为数据源。对于“日记账分录已创建”,使用 BKPF,并筛选出非冲销且最初未暂存的凭证。可通过与 VBSEGK 执行反连接来实现。
- 提取工作流事件:创建数据流,使用对象键(公司代码+凭证编号+会计年度)将 BKPF 与 SWW_WI2OBJ 连接,以查找工作流实例 ID。再将结果与 SWWLOGHIST 连接,根据日志中记录的工作流任务结果和用户决策,提取“已提交”“已批准”“已拒绝”和“已请求更改”等事件。
- 提取更改和删除事件:使用 CDHDR 和 CDPOS 表识别更改。对于“日记账分录已更正”,筛选对象类为“FIPP”的暂存凭证更改记录。对于“暂存日记账分录已删除”,查找暂存凭证更改日志中的删除标记。
- 提取附件事件:要捕获“已附加文档”事件,请将 BKPF 与 SRGBTBREL 连接,条件为对象类型是“BKPF”,关系类型是“[您的附件关系类型]”。链接的创建日期用作事件时间。
- 提取清账和冲销事件:对于“日记账分录行项目已清账”,查询 BSEG 表中清账凭证字段(AUGBL)有值的记录。事件时间为清账凭证的过账日期(AUGDT)。对于“日记账分录冲销已处理”,查询 BKPF 中的冲销凭证,即冲销凭证字段(STBLG)有值的记录。
- 派生计算事件:为计算事件分别创建逻辑块。对于“已识别手工分录”,根据手工交易代码列表(例如 FB01、FB50、F-02)筛选 BKPF。对于“已识别跨公司过账”,按凭证 ID 对 BSEG 表分组,并识别公司代码不止一个的凭证。
- 合并所有事件流:在 ETL 工具中使用 UNION 转换,将各个事件流(已创建、已暂存、已批准等)的输出合并到单个事件日志表中。确保所有事件流的列名和数据类型一致。
- 映射到最终架构:将合并后的数据映射到所需的事件日志结构,创建
JournalEntryId、ActivityName、EventTime、用户及其他必需和推荐属性。添加SourceSystem等静态列,并使用 ETL 作业的执行时间作为LastDataUpdate。 - 配置增量加载:对于后续提取,请配置增量加载策略。使用最近的创建日期或更改日期(例如 BKPF.CPUDT、CDHDR.UDATE)作为水位标记,仅提取上次运行以来新增或更新的记录。
- 导出到 ProcessMind:安排提取作业,并配置最终输出步骤,将事件日志保存为 CSV 或 Parquet 文件,存放在 ProcessMind 可访问的位置,以便上传。
配置
- 前提条件:拥有许可的第三方 ETL 工具(例如 Theobald Xtract Universal、Informatica、Talend),并配备专用 SAP 连接器。SAP 用户账户需具备 RFC 访问权限,以及读取财务表(例如表组 F_00、F_WF)、工作流数据和更改日志的授权。
- 连接参数:您需要 SAP 应用服务器 IP 地址或主机名、系统编号和客户端 ID。请使用安全的凭据管理方式保存 SAP 用户名和密码。
- 关键筛选条件:始终在数据源端按公司代码(BKPF.BUKRS)和会计年度(BKPF.GJAHR)筛选,以控制数据量。强烈建议按凭证创建日期(BKPF.CPUDT)筛选,设定明确的提取周期,例如最近 6 个月。
- 日期范围选择:首次加载时,选择具有代表性的时间段,例如 3 到 6 个月。后续增量加载可使用时间戳字段(如
CPUDT)作为水位标记,仅获取新增记录。 - 性能注意事项:BSEG、CDPOS 和工作流表上的连接可能非常耗时。请尽可能让 ETL 工具将筛选条件下推到 SAP 数据源。如果工具支持,可分批或分块提取数据,尤其是在加载大量历史数据时。
- 工作流自定义:识别“已批准”或“已拒绝”等工作流活动的逻辑,很大程度上取决于您使用的工作流模板。您需要从系统中确认正确的工作流任务 ID 和用户决策键,并将其用于筛选。
查询示例 sql
/*
This is a logical representation of the extraction configuration in a third-party ETL tool.
It is not executable SQL but defines the sources, joins, and transformations for each activity.
Placeholders like [Your SAP Source], [Date Filter], and [Company Code Filter] must be configured in the tool.
*/
-- Extraction block for 'Journal Entry Parked'
SELECT
CONCAT(v.BUKRS, v.VBELN, v.GJAHR) AS JournalEntryId,
'Journal Entry Parked' AS ActivityName,
CAST(CONCAT(v.CPUDT, v.CPUTM) AS TIMESTAMP) AS EventTime,
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
v.USNAM AS User,
v.BUKRS AS CompanyCode,
v.BLART AS DocumentType,
v.BUDAT AS PostingDate,
v.TCODE AS TransactionCode,
FALSE AS IsReversed
FROM [Your SAP Source].VBSEGK v
WHERE [Date Filter on v.CPUDT] AND [Company Code Filter on v.BUKRS]
UNION ALL
-- Extraction block for 'Journal Entry Created'
SELECT
CONCAT(h.BUKRS, h.BELNR, h.GJAHR) AS JournalEntryId,
'Journal Entry Created' AS ActivityName,
CAST(CONCAT(h.CPUDT, h.CPUTM) AS TIMESTAMP) AS EventTime,
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
h.USNAM AS User,
h.BUKRS AS CompanyCode,
h.BLART AS DocumentType,
h.BUDAT AS PostingDate,
h.TCODE AS TransactionCode,
FALSE AS IsReversed
FROM [Your SAP Source].BKPF h
LEFT JOIN [Your SAP Source].VBSEGK v ON h.AWKEY = CONCAT(v.BUKRS, v.VBELN, v.GJAHR)
WHERE h.BSTAT = '' AND v.VBELN IS NULL AND h.STBLG IS NULL
AND [Date Filter on h.CPUDT] AND [Company Code Filter on h.BUKRS]
UNION ALL
-- Extraction block for 'Journal Entry Posted' (from parked)
SELECT
CONCAT(h.BUKRS, h.BELNR, h.GJAHR) AS JournalEntryId,
'Journal Entry Posted' AS ActivityName,
CAST(CONCAT(h.CPUDT, h.CPUTM) AS TIMESTAMP) AS EventTime, -- Or a more precise posting time from change logs if available
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
h.USNAM AS User,
h.BUKRS AS CompanyCode,
h.BLART AS DocumentType,
h.BUDAT AS PostingDate,
h.TCODE AS TransactionCode,
FALSE AS IsReversed
FROM [Your SAP Source].BKPF h
JOIN [Your SAP Source].VBSEGK v ON h.AWKEY = CONCAT(v.BUKRS, v.VBELN, v.GJAHR)
WHERE [Date Filter on h.CPUDT] AND [Company Code Filter on h.BUKRS]
UNION ALL
-- Extraction block for 'Journal Entry Submitted', 'Approved', 'Rejected', 'Changes Requested'
SELECT
CONCAT(SUBSTRING(o.INSTID, 3, 4), SUBSTRING(o.INSTID, 7, 10), SUBSTRING(o.INSTID, 17, 4)) AS JournalEntryId,
CASE
WHEN wl.WI_TEXT LIKE '%Submit%' THEN 'Journal Entry Submitted'
WHEN wl.WI_TEXT LIKE '%Approve%' THEN 'Journal Entry Approved'
WHEN wl.WI_TEXT LIKE '%Reject%' THEN 'Journal Entry Rejected'
WHEN wl.WI_TEXT LIKE '%Request Changes%' THEN 'Journal Entry Changes Requested'
END AS ActivityName,
CAST(CONCAT(wl.WI_CD, wl.WI_CT) AS TIMESTAMP) AS EventTime,
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
wl.EXEC_USER AS User,
SUBSTRING(o.INSTID, 3, 4) AS CompanyCode,
NULL AS DocumentType,
NULL AS PostingDate,
NULL AS TransactionCode,
FALSE AS IsReversed
FROM [Your SAP Source].SWW_WI2OBJ o
JOIN [Your SAP Source].SWWLOGHIST wl ON o.WI_ID = wl.WI_ID
WHERE o.TYPEID = 'BKPF' AND o.CATID = 'BO'
AND wl.WI_TEXT IN ('[Your Submit Task Name]', '[Your Approve Task Name]', '[Your Reject Task Name]', '[Your Changes Request Task Name]')
AND [Date Filter on wl.WI_CD]
UNION ALL
-- Extraction block for 'Journal Entry Corrected'
SELECT
CONCAT(cd.OBJECTID_LONG_CHAR(3,4), cd.OBJECTID_LONG_CHAR(7,10), cd.OBJECTID_LONG_CHAR(17,4)) AS JournalEntryId,
'Journal Entry Corrected' AS ActivityName,
CAST(CONCAT(cd.UDATE, cd.UTIME) AS TIMESTAMP) AS EventTime,
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
cd.USERNAME AS User,
cd.OBJECTID_LONG_CHAR(3,4) AS CompanyCode,
NULL AS DocumentType,
NULL AS PostingDate,
cd.TCODE AS TransactionCode,
FALSE AS IsReversed
FROM [Your SAP Source].CDHDR cd
WHERE cd.OBJECTCLAS = 'FIPP' AND cd.CHANGE_IND = 'U'
AND [Date Filter on cd.UDATE]
UNION ALL
-- Extraction block for 'Parked Journal Entry Deleted'
SELECT
CONCAT(cd.OBJECTID_LONG_CHAR(3,4), cd.OBJECTID_LONG_CHAR(7,10), cd.OBJECTID_LONG_CHAR(17,4)) AS JournalEntryId,
'Parked Journal Entry Deleted' AS ActivityName,
CAST(CONCAT(cd.UDATE, cd.UTIME) AS TIMESTAMP) AS EventTime,
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
cd.USERNAME AS User,
cd.OBJECTID_LONG_CHAR(3,4) AS CompanyCode,
NULL AS DocumentType,
NULL AS PostingDate,
cd.TCODE AS TransactionCode,
FALSE AS IsReversed
FROM [Your SAP Source].CDHDR cd
WHERE cd.OBJECTCLAS = 'FIPP' AND cd.CHANGE_IND = 'D'
AND [Date Filter on cd.UDATE]
UNION ALL
-- Extraction block for 'Documentation Attached'
SELECT
CONCAT(SUBSTRING(r.INSTID_A, 3, 4), SUBSTRING(r.INSTID_A, 7, 10), SUBSTRING(r.INSTID_A, 17, 4)) AS JournalEntryId,
'Documentation Attached' AS ActivityName,
-- Note: A precise timestamp is often unavailable. Using document creation time as a proxy.
CAST(CONCAT(h.CPUDT, h.CPUTM) AS TIMESTAMP) AS EventTime,
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
h.USNAM AS User,
h.BUKRS AS CompanyCode,
h.BLART AS DocumentType,
h.BUDAT AS PostingDate,
h.TCODE AS TransactionCode,
FALSE AS IsReversed
FROM [Your SAP Source].SRGBTBREL r
JOIN [Your SAP Source].BKPF h ON h.BUKRS = SUBSTRING(r.INSTID_A, 3, 4) AND h.BELNR = SUBSTRING(r.INSTID_A, 7, 10) AND h.GJAHR = SUBSTRING(r.INSTID_A, 17, 4)
WHERE r.TYPEID_A = 'BKPF' AND r.RELTYPE = '[Configure based on your system]'
AND [Date Filter on h.CPUDT] AND [Company Code Filter on h.BUKRS]
UNION ALL
-- Extraction block for 'Journal Entry Reversal Processed'
SELECT
CONCAT(h.BUKRS, h.BELNR, h.GJAHR) AS JournalEntryId,
'Journal Entry Reversal Processed' AS ActivityName,
CAST(CONCAT(h.CPUDT, h.CPUTM) AS TIMESTAMP) AS EventTime,
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
h.USNAM AS User,
h.BUKRS AS CompanyCode,
h.BLART AS DocumentType,
h.BUDAT AS PostingDate,
h.TCODE AS TransactionCode,
TRUE AS IsReversed
FROM [Your SAP Source].BKPF h
WHERE h.STBLG IS NOT NULL AND h.STBLG <> ''
AND [Date Filter on h.CPUDT] AND [Company Code Filter on h.BUKRS]
UNION ALL
-- Extraction block for 'Is Reversed' flag on original document
SELECT
CONCAT(h_orig.BUKRS, h_orig.BELNR, h_orig.GJAHR) AS JournalEntryId,
'Is Reversed' AS ActivityName, -- This is an attribute update, modeled as an event
CAST(CONCAT(h_rev.CPUDT, h_rev.CPUTM) AS TIMESTAMP) AS EventTime,
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
h_rev.USNAM AS User,
h_orig.BUKRS AS CompanyCode,
h_orig.BLART AS DocumentType,
h_orig.BUDAT AS PostingDate,
h_orig.TCODE AS TransactionCode,
TRUE AS IsReversed
FROM [Your SAP Source].BKPF h_rev
JOIN [Your SAP Source].BKPF h_orig ON h_rev.STBLG = h_orig.BELNR AND h_rev.BUKRS = h_orig.BUKRS AND h_rev.GJAHR_S = h_orig.GJAHR
WHERE h_rev.STBLG IS NOT NULL AND h_rev.STBLG <> ''
AND [Date Filter on h_rev.CPUDT] AND [Company Code Filter on h_rev.BUKRS]
UNION ALL
-- Extraction block for 'Journal Entry Line Item Cleared'
SELECT
CONCAT(i.BUKRS, i.BELNR, i.GJAHR) AS JournalEntryId,
'Journal Entry Line Item Cleared' AS ActivityName,
CAST(i.AUGDT AS TIMESTAMP) AS EventTime,
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
NULL AS User, -- User who performed clearing is on the clearing document header
i.BUKRS AS CompanyCode,
NULL AS DocumentType,
NULL AS PostingDate,
NULL AS TransactionCode,
FALSE AS IsReversed
FROM [Your SAP Source].BSEG i
WHERE i.AUGBL IS NOT NULL AND i.AUGBL <> ''
AND [Date Filter on i.AUGDT] AND [Company Code Filter on i.BUKRS]
UNION ALL
-- Extraction block for 'Manual Entry Identified'
SELECT
CONCAT(h.BUKRS, h.BELNR, h.GJAHR) AS JournalEntryId,
'Manual Entry Identified' AS ActivityName,
CAST(CONCAT(h.CPUDT, h.CPUTM) AS TIMESTAMP) AS EventTime, -- Same time as creation
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
h.USNAM AS User,
h.BUKRS AS CompanyCode,
h.BLART AS DocumentType,
h.BUDAT AS PostingDate,
h.TCODE AS TransactionCode,
FALSE AS IsReversed
FROM [Your SAP Source].BKPF h
WHERE h.TCODE IN ('FB01', 'F-02', 'FB50', 'FV50', '[Add other manual T-Codes]')
AND [Date Filter on h.CPUDT] AND [Company Code Filter on h.BUKRS]
UNION ALL
-- Extraction block for 'Cross-Company Posting Identified'
SELECT
JournalEntryId,
'Cross-Company Posting Identified' AS ActivityName,
EventTime, -- Same time as creation
'SAP ECC' AS SourceSystem,
NOW() AS LastDataUpdate,
User,
CompanyCode,
DocumentType,
PostingDate,
TransactionCode,
IsReversed
FROM (
SELECT
CONCAT(h.BUKRS, h.BELNR, h.GJAHR) AS JournalEntryId,
CAST(CONCAT(h.CPUDT, h.CPUTM) AS TIMESTAMP) AS EventTime,
h.USNAM AS User,
h.BUKRS AS CompanyCode,
h.BLART AS DocumentType,
h.BUDAT AS PostingDate,
h.TCODE AS TransactionCode,
FALSE AS IsReversed,
(SELECT COUNT(DISTINCT i.BUKRS) FROM [Your SAP Source].BSEG i WHERE i.BELNR = h.BELNR AND i.BUKRS = h.BUKRS AND i.GJAHR = h.GJAHR) as CompanyCodeCount
FROM [Your SAP Source].BKPF h
WHERE [Date Filter on h.CPUDT] AND [Company Code Filter on h.BUKRS]
) AS CrossCompanyCheck
WHERE CompanyCodeCount > 1 立即优化记录到报告流程中的日记账分录环节
将日记账分录周期时间缩短30%,确保报告准确无误。
无需信用卡,立即开始改进。