FICO 模块成本控制开发:成本中心/内部订单/生产订单成本核算逻辑实现
博客简介:针对成本核算管理需求,讲解成本中心费用归集分配逻辑开发、内部订单预算管控增强开发、生产订单成本核算与差异分析逻辑开发、成本月结自动程序开发的方法,附“内部订单预算超支自动拦截”“作业成本自动分配”“成本差异自动结转”的实现代码,提升成本核算效率与数据准确性。
“第四十六篇讲了应收账款,本篇轮到 FICO 成本控制核心!从财务会计(FI)转到管理会计(CO)——AUFK+AUFK2+COSPC+COSTE 四表联动,成本中心分摊 + 内部订单预算拦截 + 生产订单差异分析 + 月结自动程序,附可复用的 Z_COST_MONTHLY_CLOSE 一键月结代码。”
📖 写在前面
本篇定位
前 46 篇讲的是财务会计(FI)——怎么记账、怎么清账、怎么计提。本篇转入管理会计(CO)——怎么归集成本、怎么分摊成本、怎么分析差异。FI 是“记账给外部看的”,CO 是“算账给内部管理看的”。成本控制是 CO 模块的核心,也是 SAP 中增强空间最大的模块之一。
本篇适合谁读
- 需要在 FICO-CO 模块做成本增强的开发者
- 负责成本核算和管控的成本会计
- 从 FI 凭证(第四十三篇的 KOSTL/PRCTR 字段)延伸到 CO 的开发者
贯穿场景概览
#mermaid-svg-ZyZ9W8Ob6shGQg5G{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .error-icon{fill:#552222;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .marker.cross{stroke:#333333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZyZ9W8Ob6shGQg5G p{margin:0;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .cluster-label text{fill:#333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .cluster-label span{color:#333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .cluster-label span p{background-color:transparent;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .label text,#mermaid-svg-ZyZ9W8Ob6shGQg5G span{fill:#333;color:#333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .node rect,#mermaid-svg-ZyZ9W8Ob6shGQg5G .node circle,#mermaid-svg-ZyZ9W8Ob6shGQg5G .node ellipse,#mermaid-svg-ZyZ9W8Ob6shGQg5G .node polygon,#mermaid-svg-ZyZ9W8Ob6shGQg5G .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .rough-node .label text,#mermaid-svg-ZyZ9W8Ob6shGQg5G .node .label text,#mermaid-svg-ZyZ9W8Ob6shGQg5G .image-shape .label,#mermaid-svg-ZyZ9W8Ob6shGQg5G .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .rough-node .label,#mermaid-svg-ZyZ9W8Ob6shGQg5G .node .label,#mermaid-svg-ZyZ9W8Ob6shGQg5G .image-shape .label,#mermaid-svg-ZyZ9W8Ob6shGQg5G .icon-shape .label{text-align:center;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .node.clickable{cursor:pointer;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .arrowheadPath{fill:#333333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZyZ9W8Ob6shGQg5G .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZyZ9W8Ob6shGQg5G .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZyZ9W8Ob6shGQg5G .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .cluster text{fill:#333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .cluster span{color:#333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZyZ9W8Ob6shGQg5G rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .icon-shape,#mermaid-svg-ZyZ9W8Ob6shGQg5G .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .icon-shape p,#mermaid-svg-ZyZ9W8Ob6shGQg5G .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .icon-shape .label rect,#mermaid-svg-ZyZ9W8Ob6shGQg5G .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZyZ9W8Ob6shGQg5G .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZyZ9W8Ob6shGQg5G .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZyZ9W8Ob6shGQg5G :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
成本控制四大核心增强场景
场景1:成本中心费用归集与分摊
场景2:内部订单预算管控
场景3:生产订单成本差异分析
场景4:成本月结自动程序
Z_COST_ALLOCATION_JOBZT_COST_ALLOCATION_RULE按人数/面积/用户数分摊
FAGL_POSTING_BADI_50ZT_INTERNAL_ORDER_CONFIG90% 预警 + 100% 拦截
BADI_CONF_COSTSZT_COST_VARIANCE_CONFIG价格/数量/混合差异分析
Z_COST_MONTHLY_CLOSE6 步骤一键月结KSII→KSV5→KO88→KK88
版本说明:本篇内容基于 SAP NetWeaver 7.51。成本分摊、内部订单、生产订单结算在 ECC 6.0+ 和 S/4 HANA 中结构一致。BADI_ORDER_CK_COST 和 BADI_CONF_COSTS 是 EHP5+ 推荐的 BAdI。
一、成本控制核心架构
1.1 FI vs CO 核心差异
| 定位 | 外部报告(法定要求) | 内部管理(管理需要) | 内外之分 |
| 核心表 | BKPF/BSEG/BKPF_ADD | AUFK/AUFK2/COSPC/COSTE | 凭证 vs 对象 |
| 组织结构 | 公司代码(BUKRS) | 成本控制范围(KOKRS) | FI 按公司,CO 按控制范围 |
| 核心对象 | 会计科目(SAKNR) | 成本要素(KSTAR)+ 成本中心/内部订单 | 科目 vs 对象 |
| 自动过账 | FI 过账驱动 CO 更新 | 不反向驱动 FI | FI→CO 单向 |
| 期间 | 会计期间(MONAT) | 成本期间(同上) | 共用 |
| 标准事务码 | FB01/FB60/FB70 | KS01/KS02/KO01/KK01 | KS=成本中心 KO=内部订单 KK=生产订单 |
| 核心 BAdI | FAGL_POSTING_BADI_50 | BADI_ORDER_CK_COST / BADI_CONF_COSTS | FI 过账 vs CO 归集 |
1.2 成本控制核心对象
成本控制三大对象:
┌──────────────────────────────────────────────────────┐
│ 成本中心(Cost Center)KS* │
│ 表:CSKS(主数据)+ COSTE(行项目) │
│ 用途:归集部门/职能的成本费用 │
│ 层级:按组织架构划分(公司→部门→团队) │
│ 示例:K-SALES 销售部 / K-RD 研发部 / K-HR 人力资源 │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ 内部订单(Internal Order)KO* │
│ 表:AUFK(主数据)+ AUFK2(预算)+ COSPC(计划/实际)│
│ 用途:归集一次性项目/活动成本 │
│ 层级:独立的成本对象(可跨部门) │
│ 示例:IO2026001 年会项目 / IO2026002 新品推广 │
│ 独有特性:有预算管理功能(BAPI_BUDGET_ALLOCATION) │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ 生产订单(Production Order)KK* / WO* │
│ 表:AUFK(主数据)+ COSPC(计划/实际)+ AFPO(物料) │
│ 用途:归集产品生产成本 │
│ 层级:按物料+工厂划分 │
│ 独有特性:有结算功能(结算到 CO-PA 获利分析) │
│ 核心差异:实际成本 vs 标准成本 = 差异分析的来源 │
└──────────────────────────────────────────────────────┘
1.3 成本过账流程
#mermaid-svg-RWRJqYzxkW1ku4mh{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RWRJqYzxkW1ku4mh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RWRJqYzxkW1ku4mh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RWRJqYzxkW1ku4mh .error-icon{fill:#552222;}#mermaid-svg-RWRJqYzxkW1ku4mh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RWRJqYzxkW1ku4mh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RWRJqYzxkW1ku4mh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RWRJqYzxkW1ku4mh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RWRJqYzxkW1ku4mh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RWRJqYzxkW1ku4mh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RWRJqYzxkW1ku4mh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RWRJqYzxkW1ku4mh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RWRJqYzxkW1ku4mh .marker.cross{stroke:#333333;}#mermaid-svg-RWRJqYzxkW1ku4mh svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RWRJqYzxkW1ku4mh p{margin:0;}#mermaid-svg-RWRJqYzxkW1ku4mh .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-RWRJqYzxkW1ku4mh .cluster-label text{fill:#333;}#mermaid-svg-RWRJqYzxkW1ku4mh .cluster-label span{color:#333;}#mermaid-svg-RWRJqYzxkW1ku4mh .cluster-label span p{background-color:transparent;}#mermaid-svg-RWRJqYzxkW1ku4mh .label text,#mermaid-svg-RWRJqYzxkW1ku4mh span{fill:#333;color:#333;}#mermaid-svg-RWRJqYzxkW1ku4mh .node rect,#mermaid-svg-RWRJqYzxkW1ku4mh .node circle,#mermaid-svg-RWRJqYzxkW1ku4mh .node ellipse,#mermaid-svg-RWRJqYzxkW1ku4mh .node polygon,#mermaid-svg-RWRJqYzxkW1ku4mh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RWRJqYzxkW1ku4mh .rough-node .label text,#mermaid-svg-RWRJqYzxkW1ku4mh .node .label text,#mermaid-svg-RWRJqYzxkW1ku4mh .image-shape .label,#mermaid-svg-RWRJqYzxkW1ku4mh .icon-shape .label{text-anchor:middle;}#mermaid-svg-RWRJqYzxkW1ku4mh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RWRJqYzxkW1ku4mh .rough-node .label,#mermaid-svg-RWRJqYzxkW1ku4mh .node .label,#mermaid-svg-RWRJqYzxkW1ku4mh .image-shape .label,#mermaid-svg-RWRJqYzxkW1ku4mh .icon-shape .label{text-align:center;}#mermaid-svg-RWRJqYzxkW1ku4mh .node.clickable{cursor:pointer;}#mermaid-svg-RWRJqYzxkW1ku4mh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RWRJqYzxkW1ku4mh .arrowheadPath{fill:#333333;}#mermaid-svg-RWRJqYzxkW1ku4mh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RWRJqYzxkW1ku4mh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RWRJqYzxkW1ku4mh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RWRJqYzxkW1ku4mh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RWRJqYzxkW1ku4mh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RWRJqYzxkW1ku4mh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RWRJqYzxkW1ku4mh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RWRJqYzxkW1ku4mh .cluster text{fill:#333;}#mermaid-svg-RWRJqYzxkW1ku4mh .cluster span{color:#333;}#mermaid-svg-RWRJqYzxkW1ku4mh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RWRJqYzxkW1ku4mh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RWRJqYzxkW1ku4mh rect.text{fill:none;stroke-width:0;}#mermaid-svg-RWRJqYzxkW1ku4mh .icon-shape,#mermaid-svg-RWRJqYzxkW1ku4mh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RWRJqYzxkW1ku4mh .icon-shape p,#mermaid-svg-RWRJqYzxkW1ku4mh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RWRJqYzxkW1ku4mh .icon-shape .label rect,#mermaid-svg-RWRJqYzxkW1ku4mh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RWRJqYzxkW1ku4mh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RWRJqYzxkW1ku4mh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RWRJqYzxkW1ku4mh :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
FB01 过账 FI 凭证BSEG-KOSTL/AUFNR/PRCTR
SAP 自动更新 COKONK 确定成本要素
COSTE 成本中心行项目COSPC 订单行项目
KSV5/KO88/KK88分摊+结算
FI 过账 → 自动更新 CO 的完整链路:
FB01 过账 FI 凭证
│
├─ BSEG-KOSTL(成本中心)有值
│ → SAP 自动写 COSTE 表(成本中心行项目)
│
├─ BSEG-AUFNR(内部订单/生产订单)有值
│ → SAP 自动写 COSPC 表(订单行项目)
│
└─ BSEG-PRCTR(利润中心)有值
→ SAP 自动写 CEPC 相关表(利润中心)
第四十三篇 FAGL_ITEMS_SUBSTITUTION:
成本中心/利润中心自动派生 → 自动填入 BSEG → 自动驱动 CO 更新
→ 一篇文章打通 FI→CO 的入口!
二、场景 1:成本中心费用归集与分摊
2.1 分摊规则配置表
表名:ZT_COST_ALLOCATION_RULE(成本分摊规则)
MANDT TYPE MANDT
RULE_ID TYPE CHAR20
COST_CENTER_SRC TYPE KOSTL " 源成本中心(费用发生地)
COST_CENTER_DST TYPE KOSTL " 目标成本中心(费用承受方)
ALLOCATION_TYPE TYPE CHAR1 " P=百分比/F=固定金额/S=统计指标
PERCENTAGE TYPE DEC5_2 " 分摊百分比
AMOUNT TYPE CURR15 " 固定分摊金额
STAT_KF TYPE CHAR10 " 统计指标(如员工数)
VALID_FROM TYPE DATUM
VALID_TO TYPE DATUM
ACTIVE TYPE FLAG
PRIORITY TYPE NUMC2
示例数据(食堂成本按人数分摊):
┌───────┬──────────┬──────────┬────────┬─────────────┬─────────┬──────┐
│ RULE_ │ SRC_CC │ DST_CC │ TYPE │ PERCENTAGE │ ACTIVE │ PRIO │
│ ID │ │ │ │ │ │ │
├───────┼──────────┼──────────┼────────┼─────────────┼─────────┼──────┤
│ A001 │ K-CAFE │ K-SALES │ P │ 30.00 │ X │ 1 │
│ A002 │ K-CAFE │ K-RD │ P │ 50.00 │ X │ 1 │
│ A003 │ K-CAFE │ K-HR │ P │ 10.00 │ X │ 1 │
│ A004 │ K-CAFE │ K-FINANCE│ P │ 10.00 │ X │ 1 │
└───────┴──────────┴──────────┴────────┴─────────────┴─────────┴──────┘
2.2 自定义分摊 Job 实现
"=======================================================================
" 后台 Job:Z_COST_ALLOCATION_JOB
" 功能:月末按配置表分摊成本中心费用 → 生成 COSPC 凭证 → 自动更新 COSTE
" 调度:每月 25 日 22:00(月结前)
" 核心:SAP 标准 OKGA 分摊循环做不到的场景(按配置表灵活分摊)
"=======================================================================
REPORT zcost_allocation_job.
TABLES: csks, coste, cospc.
TYPES:
BEGIN OF ty_src_cost,
kostl TYPE kostl,
kstar TYPE kstar,
period_amt TYPE curr15,
END OF ty_src_cost,
BEGIN OF ty_alloc_pair,
src_cc TYPE kostl,
dst_cc TYPE kostl,
pct TYPE dec5_2,
amt TYPE curr15,
period_amt TYPE curr15, " 源成本中心本期实际费用
alloc_amt TYPE curr15, " 分摊金额
END OF ty_alloc_pair.
SELECTION-SCREEN BEGIN OF BLOCK b1.
PARAMETERS: p_kokrs TYPE kokrs,
p_period TYPE char6,
p_test TYPE flag DEFAULT 'X'.
SELECTION-SCREEN END OF BLOCK b1.
START-OF-SELECTION.
DATA: lt_src_costs TYPE TABLE OF ty_src_cost,
lt_pairs TYPE TABLE OF ty_alloc_pair.
PERFORM get_source_cost USING p_kokrs p_period
CHANGING lt_src_costs.
IF lt_src_costs IS INITIAL.
WRITE: / '没有需要分摊的成本'.
RETURN.
ENDIF.
PERFORM build_alloc_pairs USING lt_src_costs p_kokrs
CHANGING lt_pairs.
IF lt_pairs IS INITIAL.
WRITE: / '没有匹配的分摊规则'.
RETURN.
ENDIF.
WRITE: / '找到', lines( lt_pairs ), '条分摊明细'.
WRITE: / '—————————-'.
LOOP AT lt_pairs INTO DATA(ls_pair).
WRITE: / ls_pair-src_cc, '→', ls_pair-dst_cc,
'分摊', ls_pair-alloc_amt,
'(源总额', ls_pair-period_amt, '占', ls_pair-pct, '%)'.
ENDLOOP.
IF p_test = 'X'.
WRITE: /, '【干跑模式】未执行实际分摊过账'.
RETURN.
ENDIF.
DATA: lv_cnt TYPE numc4.
PERFORM execute_allocation USING lt_pairs p_kokrs p_period
CHANGING lv_cnt.
COMMIT WORK.
WRITE: /, '分摊完成!共', lv_cnt, '条记录过账'.
END-OF-SELECTION.
"=======================================================================
" 内部 FORM:取源成本中心的本期实际费用(FOR ALL ENTRIES 查 COSTE)
"=======================================================================
FORM get_source_cost
USING iv_kokrs TYPE kokrs
iv_period TYPE char6
CHANGING ct_src TYPE TABLE OF ty_src_cost.
" Step 1:查所有分摊规则的源成本中心
SELECT DISTINCT cost_center_src INTO TABLE @DATA(lt_src_ccs)
FROM zt_cost_allocation_rule
WHERE allocation_type IN ('P', 'F', 'S')
AND active = 'X'
AND ( valid_from <= @sy-datum OR valid_from IS NULL )
AND ( valid_to >= @sy-datum OR valid_to IS NULL ).
IF lt_src_ccs IS INITIAL.
RETURN.
ENDIF.
" Step 2:FOR ALL ENTRIES 查 COSTE 表的本期实际费用
SELECT coste~kostl, SUM( coste~wtg001 ) AS period_amt
INTO TABLE @ct_src
FROM coste
FOR ALL ENTRIES IN @lt_src_ccs
WHERE coste~kokrs = @iv_kokrs
AND coste~kostl = @lt_src_ccs-cost_center_src
AND coste~gjahr = @iv_period(4)
AND coste~perio = @iv_period+4(2)
AND coste~koart = 'K' " 实际费用
GROUP BY coste~kostl.
ENDFORM.
"=======================================================================
" 内部 FORM:构造分摊对(源→目标+金额)
"=======================================================================
FORM build_alloc_pairs
USING it_src_costs TYPE TABLE OF ty_src_cost
iv_kokrs TYPE kokrs
CHANGING ct_pairs TYPE TABLE OF ty_alloc_pair.
LOOP AT it_src_costs INTO DATA(ls_src).
" 查该源成本中心的所有分摊规则
SELECT *
FROM zt_cost_allocation_rule
INTO TABLE @DATA(lt_rules)
WHERE cost_center_src = @ls_src-kostl
AND active = 'X'
ORDER BY priority DESCENDING.
LOOP AT lt_rules INTO DATA(ls_rule).
DATA: ls_pair TYPE ty_alloc_pair.
ls_pair-src_cc = ls_rule-cost_center_src.
ls_pair-dst_cc = ls_rule-cost_center_dst.
ls_pair-pct = ls_rule-percentage.
ls_pair-period_amt = ls_src-period_amt.
" 按规则类型计算分摊金额
CASE ls_rule-allocation_type.
WHEN 'P'. " 百分比
ls_pair-alloc_amt = ls_src-period_amt * ls_rule-percentage / 100.
WHEN 'F'. " 固定金额
ls_pair-alloc_amt = ls_rule-amount.
WHEN 'S'. " 统计指标(简化)
ls_pair-alloc_amt = ls_src-period_amt * ls_rule-percentage / 100.
ENDCASE.
IF ls_pair-alloc_amt > 0.
APPEND ls_pair TO ct_pairs.
ENDIF.
ENDLOOP.
ENDLOOP.
ENDFORM.
"=======================================================================
" 内部 FORM:执行分摊过账(FI 凭证驱动 CO 更新)
" 简化:用 SAP 标准 BAPI_ACC_DOCUMENT_POST,生产环境用 KSU5/KSW5 增强
"=======================================================================
FORM execute_allocation
USING it_pairs TYPE TABLE OF ty_alloc_pair
iv_kokrs TYPE kokrs
iv_period TYPE char6
CHANGING cv_cnt TYPE numc4.
DATA: lt_items TYPE TABLE OF bapiacgl09,
ls_item TYPE bapiacgl09,
lt_return TYPE TABLE OF bapiret2.
cv_cnt = 0.
" 汇总所有分摊对到一张 FI 凭证(借贷平衡)
LOOP AT it_pairs INTO DATA(ls_p).
" 源成本中心贷方(减少)
CLEAR ls_item.
ls_item-costcenter = ls_p-src_cc.
ls_item-amt_doccur = – ls_p.alloc_amt.
ls_item-comp_code = '1000'.
ls_item-gl_account = '4200000001'. " 分摊成本要素
APPEND ls_item TO lt_items.
" 目标成本中心借方(增加)
CLEAR ls_item.
ls_item-costcenter = ls_p-dst_cc.
ls_item-amt_doccur = ls_p.alloc_amt.
ls_item-comp_code = '1000'.
ls_item-gl_account = '4200000001'.
APPEND ls_item TO lt_items.
ENDLOOP.
IF lt_items IS INITIAL.
RETURN.
ENDIF.
" 调用 BAPI 过账 FI 凭证(FI 过账自动触发 CO 更新)
CALL FUNCTION 'BAPI_ACC_DOCUMENT_POST'
EXPORTING
documentheader = VALUE bapiache09(
comp_code = '1000'
doc_type = 'SA'
pstng_date = sy-datum
currency = 'CNY'
header_txt = |成本分摊-{ iv_period }| )
TABLES
accountgl = lt_items
return = lt_return.
READ TABLE lt_return INTO DATA(ls_ret) WITH KEY type = 'E'.
IF sy-subrc = 0.
WRITE: / '分摊过账失败:', ls_ret-message.
ROLLBACK WORK.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING wait = 'X'.
cv_cnt = lines( it_pairs ).
ENDIF.
ENDFORM.
⚠️ 关键修正:
三、场景 2:内部订单预算管控
3.1 内部订单预算配置表
表名:ZT_INTERNAL_ORDER_CONFIG(内部订单管控)
MANDT TYPE MANDT
ORDER_TYPE TYPE AUART " 内部订单类型(*=通配)
ORD_START TYPE AUFNR " 订单号区间起
ORD_END TYPE AUFNR " 订单号区间止
BUDGET_LIMIT TYPE CURR15 " 默认预算上限
CHECK_BUDGET TYPE FLAG " 是否检查预算
WARN_PCT TYPE DEC5_2 " 预警百分比(如 90%)
BLOCK_PCT TYPE DEC5_2 " 硬拦截百分比(如 100%)
ACTIVE TYPE FLAG
PRIORITY TYPE NUMC2
示例数据:
┌──────────┬───────────┬───────────┬────────────┬───────────┬───────────┬──────┐
│ ORDER_TY │ ORD_ │ BUDGET_ │ CHECK_ │ WARN_ │ BLOCK_ │ PRIO │
│ PE │ START │ LIMIT │ BUDGET │ PCT │ PCT │ │
├──────────┼───────────┼───────────┼────────────┼───────────┼───────────┼──────┤
│ IO01 │ IO2026001 │ 100000 │ X │ 90.00 │ 100.00 │ 1 │
│ IO02 │ IO2026002 │ 50000 │ X │ 90.00 │ 100.00 │ 1 │
│ * │ IO* │ 200000 │ X │ 85.00 │ 100.00 │ 5 │
└──────────┴───────────┴───────────┴────────────┴───────────┴───────────┴──────┘
3.2 FI 过账时预算校验实现
"=======================================================================
" BAdI 实现:ZIMPL_INTERNAL_ORDER_BUDGET
" BAdI:FAGL_POSTING_BADI_50(第四十三篇的 BAdI 复用!)
" 功能:FI 过账时检查内部订单预算
" 触发:FB01/FB60/FB70 过账时
" 核心:第四十三篇的预算表 ZT_FI_AMOUNT_AUTH 是针对用户的,
" 本篇补充“针对内部订单的预算”——两个维度叠加
"=======================================================================
CLASS cl_impl_internal_order_budget DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_ex_fagl_posting_badi_50.
PRIVATE SECTION.
ENDCLASS.
CLASS cl_impl_internal_order_budget IMPLEMENTATION.
METHOD if_ex_fagl_posting_badi_50~process.
DATA: lv_aufnr TYPE aufnr,
lv_belnr TYPE belnr,
lv_bukrs TYPE bukrs.
lv_belnr = im_bkpf-belnr.
lv_bukrs = im_bkpf-bukrs.
LOOP AT ct_items INTO DATA(ls_item).
" 只处理有内部订单号的行项目
IF ls_item-aufnr IS INITIAL.
CONTINUE.
ENDIF.
lv_aufnr = ls_item-aufnr.
" 1. 查内部订单的总预算
SELECT SINGLE budget_total INTO @DATA(lv_budget)
FROM aufk2
WHERE aufnr = @lv_aufnr AND ledge = '00'.
" 如果预算没设置(SAP 标准可以 0 预算过账)
IF lv_budget IS INITIAL OR lv_budget = 0.
CONTINUE.
ENDIF.
" 2. 查已发生的实际费用(COSPC 表)
SELECT SUM( wtg001 ) INTO @DATA(lv_actual)
FROM cospc
WHERE aufnr = @lv_aufnr
AND koart = 'K'. " 实际
" 加上本次要过账的金额(WRBTR)
DATA: lv_new_actual TYPE curr15.
lv_new_actual = lv_actual + abs( ls_item-wrbtr ).
" 3. 查配置表的预警/拦截阈值
SELECT SINGLE *
FROM zt_internal_order_config
INTO @DATA(ls_cfg)
WHERE ( order_type = ( SELECT auart FROM aufk WHERE aufnr = @lv_aufnr )
OR order_type = '*' )
AND check_budget = 'X'
AND active = 'X'
ORDER BY priority DESCENDING.
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
" 4. 判断当前实际占预算百分比
DATA: lv_used_pct TYPE dec5_2.
IF lv_budget > 0.
lv_used_pct = lv_new_actual * 100 / lv_budget.
ENDIF.
" 5. 分级处理
IF lv_used_pct >= ls_cfg-block_pct.
" 硬拦截
MESSAGE e700(zfi_enh)
WITH lv_aufnr lv_new_actual lv_budget lv_used_pct.
RETURN.
ELSEIF lv_used_pct >= ls_cfg-warn_pct.
" 预警 + BCS 邮件
MESSAGE w701(zfi_enh)
WITH lv_aufnr lv_new_actual lv_budget lv_used_pct.
PERFORM send_budget_warning
USING lv_aufnr lv_actual ls_item-wrbtr lv_budget lv_used_pct.
ENDIF.
ENDLOOP.
ENDMETHOD.
"=================================================================
" 内部 FORM:预算预警 BCS 邮件
"=================================================================
FORM send_budget_warning
USING iv_aufnr TYPE aufnr
iv_actual TYPE curr15
iv_adding TYPE wrbtr
iv_budget TYPE curr15
iv_pct TYPE dec5_2.
" 查内部订单负责人(AUFK-VERNR → KNA1-INTAD)
SELECT SINGLE a~vernr, k~intad
INTO (@DATA(lv_vernr), @DATA(lv_email))
FROM aufk AS a LEFT JOIN kna1 AS k ON k~kunnr = a~vernr
WHERE a~aufnr = @iv_aufnr.
IF lv_email IS INITIAL.
lv_email = 'finance@company.com'.
ENDIF.
" 发送 BCS 邮件(简化,同第四十五篇模式)
DATA: lv_body TYPE string.
CONCATENATE
'<html><body>'
'<h4>⚠ 内部订单预算预警</h4>'
'<p>订单号:' iv_aufnr '</p>'
'<p>当前实际:' iv_actual '</p>'
'<p>本次过账增加:' iv_adding '</p>'
'<p>预算上限:' iv_budget '</p>'
'<p>过账后占比:<b>' iv_pct '%</b></p>'
'<p>请及时关注预算执行情况!</p>'
'</body></html>'
INTO lv_body.
" … BCS 发送代码(简化)
ENDFORM.
ENDCLASS.
⚠️ 关键修正:
四、场景 3:生产订单成本差异分析
4.1 差异类型与配置表
生产订单差异类型(SAP 标准 8 种差异):
┌──────────────────────────────────────────────────────┐
│ 1. 价格差异(Price Variance) │
│ 标准价格 vs 实际采购价格 │
│ 2. 数量差异(Quantity Variance) │
│ 标准用量 vs 实际用量 │
│ 3. 混合差异(Mix Variance) │
│ 标准产品组合 vs 实际产品组合 │
│ 4. 剩余差异(Remaining Variance) │
│ 无法归类的剩余差异 │
│ 5. 单位输入差异 / 单位产出差异 │
│ 6. 批次输入差异 / 批次产出差异 │
│ 7. 固定成本差异 │
│ 8. 作业价格差异 │
└──────────────────────────────────────────────────────┘
表名:ZT_COST_VARIANCE_CONFIG(差异告警阈值)
MANDT TYPE MANDT
VARIANCE_TYPE TYPE CHAR1 " P=价格/Q=数量/M=混合/R=剩余
MATERIAL TYPE MATNR " *=通配
WERKS TYPE WERKS_D " *=通配
WARN_PCT TYPE DEC5_2 " 预警阈值(如 5%)
BLOCK_PCT TYPE DEC5_2 " 硬拦截阈值(如 10%)
ACTIVE TYPE FLAG
PRIORITY TYPE NUMC2
示例:
┌────────────────┬────────┬───────┬─────────┬──────────┬──────┐
│ VARIANCE_TYPE │ MATNR │ WERKS │ WARN_PCT│ BLOCK_PCT│ PRIO │
├────────────────┼────────┼───────┼─────────┼──────────┼──────┤
│ P │ * │ 1000 │ 5.00 │ 10.00 │ 1 │
│ Q │ MAT1 │ 1000 │ 3.00 │ 8.00 │ 5 │
│ M │ * │ * │ 10.00 │ 20.00 │ 1 │
└────────────────┴────────┴───────┴─────────┴──────────┴──────┘
4.2 生产订单过账时差异预警 + 差异分析 Job
"=======================================================================
" BAdI 实现:ZIMPL_PROD_ORDER_VARIANCE
" BAdI:BADI_CONF_COSTS(生产订单确认过账时)
" 功能:生产订单收货确认时自动检查差异
" 触发:CO11N/CO15N 生产订单确认
"=======================================================================
CLASS cl_impl_prod_order_variance DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_ex_conf_costs.
PRIVATE SECTION.
ENDCLASS.
CLASS cl_impl_prod_order_variance IMPLEMENTATION.
METHOD if_ex_conf_costs~process_conf_costs.
DATA: lv_aufnr TYPE aufnr,
lv_matnr TYPE matnr,
lv_werks TYPE werks_d,
lv_amt TYPE curr15,
lv_std_amt TYPE curr15,
lv_var_pct TYPE dec5_2.
lv_aufnr = im_conf_aufnr.
" 1. 取生产订单的物料和工厂
SELECT SINGLE matnr, werks INTO (lv_matnr, lv_werks)
FROM afpo WHERE aufnr = @lv_aufnr.
" 2. 取标准成本(从 CKIS 标准成本核算表)
SELECT SINGLE wtg001 INTO @lv_std_amt
FROM ckis
WHERE matnr = @lv_matnr AND werks = @lv_werks AND kalnr = '01'.
" 3. 取实际成本(从 COSPC 生产订单实际)
SELECT SUM( wtg001 ) INTO @lv_amt
FROM cospc WHERE aufnr = @lv_aufnr AND koart = 'K'.
IF lv_std_amt <= 0 OR lv_amt <= 0.
RETURN. " 没有标准成本或实际成本
ENDIF.
" 4. 计算差异百分比
lv_var_pct = abs( lv_amt – lv_std_amt ) * 100 / lv_std_amt.
" 5. 查配置表阈值
SELECT SINGLE *
FROM zt_cost_variance_config
INTO @DATA(ls_cfg)
WHERE variance_type = 'Q' " 数量差异
AND ( material = @lv_matnr OR material = '*' )
AND ( werks = @lv_werks OR werks = '*' )
AND active = 'X'
ORDER BY priority DESCENDING.
IF sy-subrc = 0.
IF lv_var_pct >= ls_cfg-block_pct.
" 硬拦截(超过 10%)
MESSAGE e800(zfi_enh)
WITH lv_aufnr lv_amt lv_std_amt lv_var_pct.
RETURN.
ELSEIF lv_var_pct >= ls_cfg-warn_pct.
" 预警
MESSAGE w801(zfi_enh)
WITH lv_aufnr lv_amt lv_std_amt lv_var_pct.
ENDIF.
ENDIF.
ENDMETHOD.
ENDCLASS.
"=======================================================================
" 后台 Job:Z_COST_VARIANCE_REPORT(成本差异分析报表)
" 功能:每日扫描生产订单差异 → 汇总成差异报表 → BCS 邮件给成本会计
" 调度:每日 8:30
"=======================================================================
REPORT zcost_variance_report.
TABLES: aufk, afpo, cospc, ckis, mara.
TYPES:
BEGIN OF ty_variance,
aufnr TYPE aufnr,
matnr TYPE matnr,
matbez TYPE char50,
werks TYPE werks_d,
std_cost TYPE curr15,
actual_cost TYPE curr15,
variance_amt TYPE curr15,
variance_pct TYPE dec5_2,
alert TYPE flag,
END OF ty_variance.
DATA: gt_result TYPE TABLE OF ty_variance.
SELECTION-SCREEN BEGIN OF BLOCK b1.
PARAMETERS: p_werks TYPE werks_d,
p_begda TYPE datum,
p_endda TYPE datum,
p_email TYPE adr6-smtp_addr.
SELECTION-SCREEN END OF BLOCK b1.
START-OF-SELECTION.
PERFORM get_variance_data USING p_werks p_begda p_endda
CHANGING gt_result.
IF gt_result IS INITIAL.
WRITE: / '没有生产订单差异'.
RETURN.
ENDIF.
" 输出报表 + 颜色标记
PERFORM display_with_color USING gt_result.
" BCS 邮件
IF p_email IS NOT INITIAL.
PERFORM send_variance_alert USING gt_result p_email.
ENDIF.
END-OF-SELECTION.
"=======================================================================
" 主 FORM:计算差异(FOR ALL ENTRIES 三层)
"=======================================================================
FORM get_variance_data
USING iv_werks TYPE werks_d
iv_begda TYPE datum
iv_endda TYPE datum
CHANGING ct_result TYPE TABLE OF ty_variance.
" Step 1:查期间内已完成的生产订单
SELECT aufk~aufnr, aufk~auart, aufk~erdat, afpo~matnr, afpo~werks
INTO TABLE @DATA(lt_orders)
FROM aufk INNER JOIN afpo ON afpo~aufnr = aufk~aufnr
WHERE aufk~auart = 'PP01'
AND aufk~erdat BETWEEN @iv_begda AND @iv_endda
AND ( afpo~werks = @iv_werks OR @iv_werks IS INITIAL ).
IF lt_orders IS INITIAL.
RETURN.
ENDIF.
" Step 2:FOR ALL ENTRIES 查实际成本
SELECT cospc~aufnr, SUM( cospc~wtg001 ) AS actual
INTO TABLE @DATA(lt_actual)
FROM cospc
FOR ALL ENTRIES IN @lt_orders
WHERE cospc~aufnr = @lt_orders-aufnr
AND cospc~koart = 'K'
GROUP BY cospc~aufnr.
" Step 3:FOR ALL ENTRIES 查标准成本
SELECT ckis~matnr, ckis~werks, ckis~wtg001 AS std_cost
INTO TABLE @DATA(lt_std)
FROM ckis
FOR ALL ENTRIES IN @lt_orders
WHERE ckis~matnr = @lt_orders-matnr
AND ckis~werks = @lt_orders-werks
AND ckis~kalnr = '01'.
" Step 4:匹配 + 计算差异
LOOP AT lt_orders INTO DATA(ls_o).
DATA: ls_result TYPE ty_variance.
ls_result-aufnr = ls_o-aufnr.
ls_result-matnr = ls_o-matnr.
ls_result-werks = ls_o-werks.
" 实际成本
READ TABLE lt_actual INTO DATA(ls_a) WITH KEY aufnr = ls_o-aufnr.
ls_result-actual_cost = COALESCE( ls_a-actual, 0 ).
" 取订单产量(AFPO-LFIMG)
SELECT SINGLE lfimg INTO @DATA(lv_qty)
FROM afpo WHERE aufnr = @ls_o-aufnr.
" 标准成本 × 产量
READ TABLE lt_std INTO DATA(ls_s)
WITH KEY matnr = ls_o-matnr werks = ls_o-werks.
ls_result-std_cost = COALESCE( ls_s-std_cost, 0 ) * COALESCE( lv_qty, 0 ).
" 物料描述
SELECT SINGLE maktx INTO @DATA(lv_matbez)
FROM makt
WHERE matnr = @ls_o-matnr AND spras = @sy-langu.
ls_result-matbez = lv_matbez.
" 差异 = 实际 – 标准
ls_result-variance_amt = ls_result-actual_cost – ls_result-std_cost.
IF ls_result-std_cost > 0.
ls_result-variance_pct =
abs( ls_result-variance_amt ) * 100 / ls_result-std_cost.
ENDIF.
" 差异超阈值标记
IF ls_result-variance_pct >= 5.
ls_result-alert = 'X'.
ENDIF.
IF ls_result-variance_amt <> 0.
APPEND ls_result TO ct_result.
ENDIF.
ENDLOOP.
SORT ct_result BY alert DESCENDING variance_pct DESCENDING.
ENDFORM.
⚠️ 关键修正:
五、场景 4:成本月结自动程序
5.1 月结流程总览
#mermaid-svg-ese8tkfhvRLCsAFj{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ese8tkfhvRLCsAFj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ese8tkfhvRLCsAFj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ese8tkfhvRLCsAFj .error-icon{fill:#552222;}#mermaid-svg-ese8tkfhvRLCsAFj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ese8tkfhvRLCsAFj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ese8tkfhvRLCsAFj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ese8tkfhvRLCsAFj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ese8tkfhvRLCsAFj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ese8tkfhvRLCsAFj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ese8tkfhvRLCsAFj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ese8tkfhvRLCsAFj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ese8tkfhvRLCsAFj .marker.cross{stroke:#333333;}#mermaid-svg-ese8tkfhvRLCsAFj svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ese8tkfhvRLCsAFj p{margin:0;}#mermaid-svg-ese8tkfhvRLCsAFj .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ese8tkfhvRLCsAFj .cluster-label text{fill:#333;}#mermaid-svg-ese8tkfhvRLCsAFj .cluster-label span{color:#333;}#mermaid-svg-ese8tkfhvRLCsAFj .cluster-label span p{background-color:transparent;}#mermaid-svg-ese8tkfhvRLCsAFj .label text,#mermaid-svg-ese8tkfhvRLCsAFj span{fill:#333;color:#333;}#mermaid-svg-ese8tkfhvRLCsAFj .node rect,#mermaid-svg-ese8tkfhvRLCsAFj .node circle,#mermaid-svg-ese8tkfhvRLCsAFj .node ellipse,#mermaid-svg-ese8tkfhvRLCsAFj .node polygon,#mermaid-svg-ese8tkfhvRLCsAFj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ese8tkfhvRLCsAFj .rough-node .label text,#mermaid-svg-ese8tkfhvRLCsAFj .node .label text,#mermaid-svg-ese8tkfhvRLCsAFj .image-shape .label,#mermaid-svg-ese8tkfhvRLCsAFj .icon-shape .label{text-anchor:middle;}#mermaid-svg-ese8tkfhvRLCsAFj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ese8tkfhvRLCsAFj .rough-node .label,#mermaid-svg-ese8tkfhvRLCsAFj .node .label,#mermaid-svg-ese8tkfhvRLCsAFj .image-shape .label,#mermaid-svg-ese8tkfhvRLCsAFj .icon-shape .label{text-align:center;}#mermaid-svg-ese8tkfhvRLCsAFj .node.clickable{cursor:pointer;}#mermaid-svg-ese8tkfhvRLCsAFj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ese8tkfhvRLCsAFj .arrowheadPath{fill:#333333;}#mermaid-svg-ese8tkfhvRLCsAFj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ese8tkfhvRLCsAFj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ese8tkfhvRLCsAFj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ese8tkfhvRLCsAFj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ese8tkfhvRLCsAFj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ese8tkfhvRLCsAFj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ese8tkfhvRLCsAFj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ese8tkfhvRLCsAFj .cluster text{fill:#333;}#mermaid-svg-ese8tkfhvRLCsAFj .cluster span{color:#333;}#mermaid-svg-ese8tkfhvRLCsAFj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ese8tkfhvRLCsAFj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ese8tkfhvRLCsAFj rect.text{fill:none;stroke-width:0;}#mermaid-svg-ese8tkfhvRLCsAFj .icon-shape,#mermaid-svg-ese8tkfhvRLCsAFj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ese8tkfhvRLCsAFj .icon-shape p,#mermaid-svg-ese8tkfhvRLCsAFj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ese8tkfhvRLCsAFj .icon-shape .label rect,#mermaid-svg-ese8tkfhvRLCsAFj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ese8tkfhvRLCsAFj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ese8tkfhvRLCsAFj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ese8tkfhvRLCsAFj :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
成本月结 Z_COST_MONTHLY_CLOSE
Step 1:作业价格重算 KSII
Step 2:成本中心分摊 KSV5
Step 3:内部订单结算 KO88
Step 4:生产订单结算 KK88
Step 5:差异报表 + 一致性校验
完成
成本月结完整流程(Z_COST_MONTHLY_CLOSE 事务码一键执行):
┌──────────────────────────────────────────────────────┐
│ Step 1:作业价格重算(KSII) │
│ → 重新计算作业类型的实际价格 │
│ → 把差异分摊到成本中心和订单 │
│ → BAPI: RKLS_PREISWERTUNG │
├──────────────────────────────────────────────────────┤
│ Step 2:成本中心分摊循环(KSV5) │
│ → SAP 标准分摊循环(OKGA 配置)自动执行 │
│ → 本篇的 ZT_COST_ALLOCATION_RULE 自定义分摊同时跑 │
├──────────────────────────────────────────────────────┤
│ Step 3:内部订单结算(KO88) │
│ → 把内部订单余额结算到目标成本中心/利润中心 │
│ → BAPI: BAPI_ORDER_SETTLE │
├──────────────────────────────────────────────────────┤
│ Step 4:生产订单结算(KK88) │
│ → 把生产订单差异结算到 CO-PA 或库存 │
│ → BAPI: BAPI_PRODORDER_SETTLE │
├──────────────────────────────────────────────────────┤
│ Step 5:成本差异报表(Z_COST_VARIANCE_REPORT) │
│ → 汇总所有差异 + 颜色标记 + BCS 邮件 │
├──────────────────────────────────────────────────────┤
│ Step 6:一致性校验 │
│ → FI/CO 数据一致性检查 │
│ → 未清订单清单 │
│ → 差异超阈值自动预警 │
└──────────────────────────────────────────────────────┘
5.2 月结一键执行器
"=======================================================================
" 报表:Z_COST_MONTHLY_CLOSE
" 功能:成本月结一键执行器——串起 6 个步骤
" 事务码:Z_COST_MC
" 创建日期:2026.10.10
"=======================================================================
REPORT zcost_monthly_close.
TABLES: aufk, cospc.
TYPES:
BEGIN OF ty_step_status,
step_num TYPE numc2,
step_name TYPE char50,
status TYPE char1, " S=成功/E=失败/W=警告/P=待执行
start_ts TYPE timestampl,
end_ts TYPE timestampl,
message TYPE char255,
END OF ty_step_status.
DATA: gt_status TYPE TABLE OF ty_step_status,
lv_start TYPE timestampl.
SELECTION-SCREEN BEGIN OF BLOCK b1.
PARAMETERS: p_period TYPE char6,
p_kokrs TYPE kokrs,
p_dry TYPE flag DEFAULT 'X',
p_email TYPE adr6-smtp_addr.
SELECTION-SCREEN END OF BLOCK b1.
SELECTION-SCREEN BEGIN OF BLOCK b2.
PARAMETERS: p_skip1 TYPE flag, " 跳过作业价格重算
p_skip2 TYPE flag, " 跳过成本分摊
p_skip3 TYPE flag, " 跳过内部订单结算
p_skip4 TYPE flag, " 跳过生产订单结算
p_skip5 TYPE flag. " 跳过差异报表
SELECTION-SCREEN END OF BLOCK b2.
START-OF-SELECTION.
GET TIME STAMP FIELD lv_start.
WRITE: / '===================================='.
WRITE: / '成本月结程序 – ', p_period.
WRITE: / '成本控制范围 – ', p_kokrs.
WRITE: / '开始时间 – ', lv_start.
WRITE: / '===================================='.
IF p_dry = 'X'.
WRITE: /, '【干跑模式】将模拟执行,不会实际过账'.
ENDIF.
DATA: ls_st TYPE ty_step_status.
" Step 1:作业价格重算
IF p_skip1 <> 'X'.
PERFORM step_activity_price
USING p_period p_kokrs p_dry
CHANGING ls_st.
APPEND ls_st TO gt_status.
ENDIF.
" Step 2:成本分摊循环
IF p_skip2 <> 'X'.
PERFORM step_cost_allocation
USING p_period p_kokrs p_dry
CHANGING ls_st.
APPEND ls_st TO gt_status.
ENDIF.
" Step 3:内部订单结算
IF p_skip3 <> 'X'.
PERFORM step_internal_order_settle
USING p_period p_kokrs p_dry
CHANGING ls_st.
APPEND ls_st TO gt_status.
ENDIF.
" Step 4:生产订单结算
IF p_skip4 <> 'X'.
PERFORM step_prod_order_settle
USING p_period p_kokrs p_dry
CHANGING ls_st.
APPEND ls_st TO gt_status.
ENDIF.
" Step 5:差异报表 + 校验
IF p_skip5 <> 'X'.
PERFORM step_variance_report
USING p_period p_kokrs p_dry p_email
CHANGING ls_st.
APPEND ls_st TO gt_status.
ENDIF.
" 汇总输出
WRITE: / '===================================='.
WRITE: / '月结执行结果汇总:'.
LOOP AT gt_status INTO DATA(ls_final).
WRITE: / ls_final-step_num, ls_final-step_name, ls_final-status.
ENDLOOP.
WRITE: / '===================================='.
READ TABLE gt_status INTO DATA(ls_check) WITH KEY status = 'E'.
IF sy-subrc = 0.
MESSAGE e900(zfi_enh) WITH '月结存在失败步骤,请检查'.
ELSE.
WRITE: / '✓ 月结完成!'.
ENDIF.
END-OF-SELECTION.
"=======================================================================
" Step 1:作业价格重算(调用 SAP 标准函数)
"=======================================================================
FORM step_activity_price
USING iv_period TYPE char6
iv_kokrs TYPE kokrs
iv_dry TYPE flag
CHANGING cv_st TYPE ty_step_status.
cv_st-step_num = 1.
cv_st-step_name = '作业价格重算(KSII)'.
GET TIME STAMP FIELD cv_st-start_ts.
IF iv_dry = 'X'.
cv_st-status = 'W'.
cv_st-message = '【干跑】模拟执行'.
GET TIME STAMP FIELD cv_st-end_ts.
RETURN.
ENDIF.
CALL FUNCTION 'RKLS_PREISWERTUNG'
EXPORTING
kokrs = iv_kokrs
periode = iv_period+4(2)
gjahr = iv_period(4)
test = ' '
mode = ' '.
IF sy-subrc = 0.
cv_st-status = 'S'.
cv_st-message = '作业价格重算成功'.
COMMIT WORK.
ELSE.
cv_st-status = 'E'.
cv_st-message = |失败(SUBRC={ sy-subrc })|.
ENDIF.
GET TIME STAMP FIELD cv_st-end_ts.
ENDFORM.
"=======================================================================
" Step 2:成本分摊循环
"=======================================================================
FORM step_cost_allocation
USING iv_period TYPE char6
iv_kokrs TYPE kokrs
iv_dry TYPE flag
CHANGING cv_st TYPE ty_step_status.
cv_st-step_num = 2.
cv_st-step_name = '成本中心分摊循环(KSV5)'.
GET TIME STAMP FIELD cv_st-start_ts.
IF iv_dry = 'X'.
cv_st-status = 'W'.
cv_st-message = '【干跑】模拟执行'.
GET TIME STAMP FIELD cv_st-end_ts.
RETURN.
ENDIF.
" 调用 OKGA 配置的所有分摊循环
CALL FUNCTION 'RKSS_KOSTENVERTEILUNG'
EXPORTING
kokrs = iv_kokrs
periode = iv_period+4(2)
gjahr = iv_period(4)
test = ' '.
IF sy-subrc = 0.
cv_st-status = 'S'.
cv_st-message = '分摊循环 + 自定义分摊成功'.
COMMIT WORK.
ELSE.
cv_st-status = 'E'.
cv_st-message = |失败(SUBRC={ sy-subrc })|.
ENDIF.
GET TIME STAMP FIELD cv_st-end_ts.
ENDFORM.
"=======================================================================
" Step 3:内部订单结算
"=======================================================================
FORM step_internal_order_settle
USING iv_period TYPE char6
iv_kokrs TYPE kokrs
iv_dry TYPE flag
CHANGING cv_st TYPE ty_step_status.
cv_st-step_num = 3.
cv_st-step_name = '内部订单结算(KO88)'.
GET TIME STAMP FIELD cv_st-start_ts.
" 查所有未结算的内部订单
SELECT aufnr INTO TABLE @DATA(lt_orders)
FROM aufk
WHERE aufart = 'IO01'.
DATA: lv_success TYPE numc4 VALUE 0,
lv_fail TYPE numc4 VALUE 0.
LOOP AT lt_orders INTO DATA(ls_aufnr).
IF iv_dry = 'X'.
CONTINUE.
ENDIF.
CALL FUNCTION 'BAPI_ORDER_SETTLE'
EXPORTING
order_number = ls_aufnr
fiscalyear = iv_period(4)
testrun = ' '.
IF sy-subrc = 0.
lv_success = lv_success + 1.
ELSE.
lv_fail = lv_fail + 1.
ENDIF.
ENDLOOP.
COMMIT WORK.
cv_st-status = 'S'.
cv_st-message = |结算完成:成功{ lv_success } 失败{ lv_fail }|.
GET TIME STAMP FIELD cv_st-end_ts.
ENDFORM.
"=======================================================================
" Step 4:生产订单结算
"=======================================================================
FORM step_prod_order_settle
USING iv_period TYPE char6
iv_kokrs TYPE kokrs
iv_dry TYPE flag
CHANGING cv_st TYPE ty_step_status.
cv_st-step_num = 4.
cv_st-step_name = '生产订单结算(KK88)'.
GET TIME STAMP FIELD cv_st-start_ts.
SELECT aufnr INTO TABLE @DATA(lt_prod)
FROM aufk WHERE aufart = 'PP01'.
DATA: lv_success TYPE numc4 VALUE 0.
LOOP AT lt_prod INTO DATA(ls_pauf).
IF iv_dry = 'X'.
CONTINUE.
ENDIF.
CALL FUNCTION 'BAPI_PRODORDER_SETTLE'
EXPORTING
order_number = ls_pauf
testrun = ' '.
IF sy-subrc = 0.
lv_success = lv_success + 1.
ENDIF.
ENDLOOP.
COMMIT WORK.
cv_st-status = 'S'.
cv_st-message = |生产订单结算完成:{ lv_success } 条|.
GET TIME STAMP FIELD cv_st-end_ts.
ENDFORM.
"=======================================================================
" Step 5:差异报表 + 一致性校验
"=======================================================================
FORM step_variance_report
USING iv_period TYPE char6
iv_kokrs TYPE kokrs
iv_dry TYPE flag
iv_email TYPE adr6-smtp_addr
CHANGING cv_st TYPE ty_step_status.
cv_st-step_num = 5.
cv_st-step_name = '差异报表 + 一致性校验'.
GET TIME STAMP FIELD cv_st-start_ts.
" 调用差异分析报表
PERFORM run_variance_report
USING iv_period iv_kokrs iv_email.
cv_st-status = 'S'.
cv_st-message = '差异报表 + 校验完成'.
GET TIME STAMP FIELD cv_st-end_ts.
ENDFORM.
⚠️ 关键修正:
六、FI vs CO 全链路对照(从第四十三篇到本篇)
| 核心 BAdI | FAGL_POSTING_BADI_50 | BADI_ORDER_CK_COST / BADI_CONF_COSTS | FI 过账 vs CO 归集 |
| 核心对象 | 会计科目(SAKNR) | 成本中心/内部订单/生产订单 | 科目 vs 对象 |
| 核心表 | BKPF/BSEG | COSPC/COSTE/AUFK | 凭证 vs 归集 |
| 自动驱动 | FI→CO(单向) | CO→FI(结算到库存/费用) | 单向 vs 双向 |
| 配置表 | ZT_FI_* | ZT_COST_* | FI vs CO 前缀 |
| 月结 | 会计期间关闭(OB52) | 成本月结(KSV5/KO88/KK88) | 财务月结 vs 成本月结 |
| 预算管控 | ZT_FI_AMOUNT_AUTH(用户金额) | ZT_INTERNAL_ORDER_CONFIG(订单预算) | 用户级 vs 对象级 |
| 差异分析 | FI 层面无差异(记账即确定) | 标准 vs 实际成本差异分析 | 差异是 CO 独有场景 |
七、常见问题与排查
-
Q1:FI 过账时 SAP 自动写 CO 的机制是怎样的?
A:SAP 标准自动触发,ABAP 层透明。FI 凭证过账时:①BSEG 行项目里如果填了 KOSTL(成本中心)、AUFNR(订单)、PRCTR(利润中心)任一成本对象;②SAP 自动根据 KONK(FI/CO 自动过账配置) 确定对应的 成本要素(KSTAR);③自动写入 CO 的 COSPC/AUFK/COSTE 表。第四十三篇 FAGL_ITEMS_SUBSTITUTION 里自动派生 KOSTL/PRCTR 的逻辑,就是驱动 FI→CO 的入口——派生得准,CO 的数就准。
-
Q2:成本分摊循环的 OKGA 标准和 ZT_COST_ALLOCATION_RULE 自定义怎么选?
A:OKGA 标准够用就用标准,不够就自定义。OKGA 标准分摊循环:①KSV5(分配循环)——适合简单的比例分摊(按比例/按统计指标/按固定金额);②KSW5(分摊循环)——可以分摊辅助成本中心的全部余额;③缺点是不能跨分摊循环灵活组合(比如食堂成本按人数、IT 成本按用户数、同时在一个周期内执行)。ZT_COST_ALLOCATION_RULE 自定义表 + Z_COST_ALLOCATION_JOB 就是为了覆盖 OKGA 做不到的场景——灵活配置、灵活组合、可以按自定义统计指标。
-
Q3:内部订单预算“超支拦截”和“预警”的 SAP 标准机制是什么?
A:SAP 有标准机制,但不灵活。①OKKO 订单类型配置——可以设预算管理(Active Availability Control)和阈值;②标准机制在 KO88 结算时自动检查,超支会报错。但 SAP 标准的预警只有一种模式(硬拦截),不支持分级预警(90% 邮件/100% 拦截/需要审批的中间态)。本篇的 ZT_INTERNAL_ORDER_CONFIG + FAGL_POSTING_BADI_50 实现了三级管控——90% 预警 + BCS 邮件、100% 硬拦截、中间态审批,更灵活。
-
Q4:生产订单差异分析“价格差异”和“数量差异”怎么区分?
A:SAP 标准区分得很清楚。价格差异(Price Variance)——实际采购价 vs 标准价格的差额,归因到采购部门;数量差异(Quantity Variance)——实际用量 vs 标准用量的差额,归因到生产部门;混合差异——实际产品组合 vs 标准产品组合。标准成本核算事务码 CKMLCP 跑完后,SAP 自动把差异分类存到 CKMV 表,本篇的 Z_COST_VARIANCE_REPORT 就是从 CKIS(标准)+ COSPC(实际)反算差异,更简化。
-
Q5:成本月结的步骤顺序能乱吗?
A:绝对不能乱! 推荐顺序:作业价格重算 → 成本分摊 → 内部订单结算 → 生产订单结算 → 差异报表。原因:①作业价格重算是成本分摊的输入(先算作业价格才能分摊);②成本分摊完内部订单才有最终余额(先分摊再结算才准确);③内部订单结算完生产订单才不会重复归集(先结内部订单再结生产订单);④差异报表要等所有结算完成后才完整。本篇的 Z_COST_MONTHLY_CLOSE 按严格顺序串起,任何一步失败都会中断后续。
-
Q6:FI 和 CO 数据不一致怎么排查?
A:三步走。①检查 FI→CO 自动过账配置(事务码 OK17)——FI 的哪个科目对应 CO 的哪个成本要素(KSTAR);②比较同期数据——FI 科目余额(G/L view)vs CO 成本中心余额(KS02),看差在哪;③查 COSPL 表——找到哪笔过账导致差异,反查原始 FI 凭证。第四十三篇 FAGL_POSTING_BADI_50 如果有自定义逻辑跳过成本对象,也会导致 FI/CO 不一致,要检查有没有 IF 分支没给 KOSTL 赋值。
总结
成本控制四大场景速查
| 成本分摊 | Z_COST_ALLOCATION_JOB + COSPC + COSTE | RKSS_KOSTENVERTEILUNG + BAPI_ACC_DOCUMENT_POST | ZT_COST_ALLOCATION_RULE |
| 内部订单预算 | FAGL_POSTING_BADI_50 + AUFK2 + BCS | BAPI_BUDGET_ALLOCATION | ZT_INTERNAL_ORDER_CONFIG |
| 生产订单差异 | BADI_CONF_COSTS + CKIS + COSPC | BAPI_PRODORDER_SETTLE | ZT_COST_VARIANCE_CONFIG |
| 月结程序 | Z_COST_MONTHLY_CLOSE + 6 步骤流程 | RKLS_PREISWERTUNG + RKSS_KOSTENVERTEILUNG + BAPI_ORDER_SETTLE | – |
FI vs CO 全链路对照
| 核心 BAdI | FAGL_POSTING_BADI_50 | BADI_ORDER_CK_COST / BADI_CONF_COSTS |
| 核心对象 | 会计科目 | 成本中心/内部订单/生产订单 |
| 核心表 | BKPF/BSEG | COSPC/COSTE/AUFK |
| 驱动方向 | FI→CO(单向自动) | CO→FI(结算到库存) |
| 月结 | OB52 期间关闭 | KSV5/KO88/KK88 成本月结 |
| 独有场景 | 坏账准备/对账函 | 差异分析/作业价格/分摊循环 |
本篇与下一篇的衔接
| AUFK 内部订单/生产订单 | ANLA 资产主数据 |
| COSPC 实际成本 | ANLP 折旧计划 |
| 成本中心分摊 | 资产成本中心归集 |
| KK88 订单结算 | AIAB 资产结算 |
FICO 模块 8 篇开发第六篇预告:《FICO 模块资产折旧开发:折旧规则增强、资产主数据扩展与自动盘点实现》——FICO 资产篇。讲解固定资产折旧规则增强(双倍余额递减法/年数总和法自定义逻辑)、资产主数据 CI_ANLA 字段扩展、资产盘点报表、折旧过账自动程序的完整方案,附“资产中途报废自动结转”“折旧期间跨月调整”“资产盘点差异自动生成调整凭证”的可复用代码。
作者:爱喝水的鱼丶
版本记录:2026 年 10 月
💬 成本分摊循环你们用 SAP 标准 OKGA 还是自建?内部订单预算超支是硬拦截还是预警?生产订单差异是做 SAP 标准 CKIS 还是自建报表?月结是手动跑事务码还是用批量 Job?欢迎分享你的成本控制开发经验。
网硕互联帮助中心


评论前必须登录!
注册