Maintain SAP S/4 Planning(ACDOCP) with custom ABAP solution
SAP Business Planning is one of the important pillar in financial management. While actual data is handled by, for the most part, automatically by SAP standard and ACDOCA is the fundamental database, there is no SAP standard way of handing planning data in S/4, namely updating ACDOCP table.
In the blog Exploring Methods to Upload Data into ACDOCP Table in SAP S/4HANA, several options are discussed and either customer have to use BPC with planning function or using SAC, or uses standard Fiori App – Import Financial Plan Data to perform Excel Upload. BPC and SAC solution are great but it requires effort to integrate with S/4 system. Fiori App – Import Financial Plan Data allows you to perform ACDOCP update inside S/4 however it does not give you flexibility to perform the update from business perspective, such as update per specific Company Code, fiscal periods, Cost Center, while looking at actual report of S/4 planning data. Instead, user has to upload the database itself in the excel sheet, for which user has to prepare database file everytime the update occurs.
This blog aims to explain step by step, a custom solution to update ACDOCP inside S/4 for specific business scenarios.
To achive this inside S/4, we will use ABAP based solution to call API API_FINPLANNINGDATA_SRV.
https://api.sap.com/api/API_FINPLANNINGDATA_SRV/path/post_FinancialPlanData
https://api.sap.com/api/API_FINPLANNINGDATA_SRV/path/post_FinancialPlanData
1. Activate API_FINPLANNINGDATA_SRV
Go to transaction /N/IWFND/MAINT_SERVICE and activate API_FINPLANNINGDATA_SRV(if it’s not done already).
Click on the SAP Gateway Client button and we can test the service.

2. Testing the API from Gateway Client
In the SAP Gateway Client, use entity set ‘FinancialPlanData’ for posting planning data. To do so, witch the HTTP method to Post.
Request URI: /sap/opu/odata/sap/API_FINPLANNINGDATA_SRV/
HTTP Request Header: application/json for both accept and content-type.
Finally set the request body in json format. The important node here is ‘PlanDataAggrgnLvlFieldsString’ which is a aggregation level of the amount you are posting to ACDOCP. For example, if you are posting 100 USD with below aggregation, that means the a single row is inserted in ACDOCP with specified Ledger, PlanningCategory, LedgerFiscalYear, GLAccount, ProfitCenter, CompanyCode, AmountInCompanyCodeCurrency, CompanyCodeCurrency and with the amount 100USD.
{
"PlanDataAggrgnLvlFieldsString" : "Ledger,PlanningCategory,LedgerFiscalYear,GLAccount,CompanyCode,AmountInCompanyCodeCurrency,CompanyCodeCurrency",
"PlanDataRplcScopeFieldsString" : "Ledger,PlanningCategory,LedgerFiscalYear,GLAccount,CompanyCode,AmountInCompanyCodeCurrency",
"to_FinPlanningEntryItemTP" : {
"results" : [
{
"ID" : "Your ID. Could be anything",
"Ledger" : "Your value",
"PlanningCategory" : "Your value",
"LedgerFiscalYear" : "Your value",
"GLAccount" : "Your value",
"CompanyCode" : "Your value",
"ControllingArea" : "Your value",
"CompanyCodeCurrency" : "Your value",
"AmountInCompanyCodeCurrency" : "Your value",
"ChartOfAccounts" : "Your value",
"ControllingDebitCreditCode" : "Your value",
"FiscalYearVariant" : "Your value"
}
]
}
}

After you press execute, the process was successful if you get return code 201.

3. How the data is updated
Let’s try to add plan data 500 EUR for certain Ledger, PlanningCategory, LedgerFiscalYear, GLAccount and CompanyCode. Below input value are dummy and please change to the value of your S/4 system.
{
"PlanDataAggrgnLvlFieldsString" : "Ledger,PlanningCategory,LedgerFiscalYear,GLAccount,CompanyCode,AmountInCompanyCodeCurrency,CompanyCodeCurrency",
"PlanDataRplcScopeFieldsString" : "Ledger,PlanningCategory,LedgerFiscalYear,GLAccount,CompanyCode,AmountInCompanyCodeCurrency",
"to_FinPlanningEntryItemTP" : {
"results" : [
{
"ID" : "PLANTEST1",
"Ledger" : "0L",
"PlanningCategory" : "PLAN",
"LedgerFiscalYear" : "2022",
"GLAccount" : "0001140000",
"CompanyCode" : "C000",
"ControllingArea" : "1000",
"CompanyCodeCurrency" : "EUR",
"AmountInCompanyCodeCurrency" : "500.00",
"ChartOfAccounts" : "CH00",
"ControllingDebitCreditCode" : "S",
"FiscalYearVariant" : "K4"
}
]
}
}
After succesfull execution, one row is added in ACDOCP with 500 EUR for the input Ledger, PlanningCategory, LedgerFiscalYear, GLAccount and CompanyCode.

Now my organization decides to change the planning amount from 500 EUR to 600 EUR, the API will not update the existing record. Instead, it will create another row that records the difference. Let’s try to post 600 EUR this time.
{
"PlanDataAggrgnLvlFieldsString" : "Ledger,PlanningCategory,LedgerFiscalYear,GLAccount,CompanyCode,AmountInCompanyCodeCurrency,CompanyCodeCurrency",
"PlanDataRplcScopeFieldsString" : "Ledger,PlanningCategory,LedgerFiscalYear,GLAccount,CompanyCode,AmountInCompanyCodeCurrency",
"to_FinPlanningEntryItemTP" : {
"results" : [
{
"ID" : "PLANTEST1",
"Ledger" : "0L",
"PlanningCategory" : "PLAN",
"LedgerFiscalYear" : "2022",
"GLAccount" : "0001140000",
"CompanyCode" : "C000",
"ControllingArea" : "1000",
"CompanyCodeCurrency" : "EUR",
"AmountInCompanyCodeCurrency" : "500.00",
"ChartOfAccounts" : "CH00",
"ControllingDebitCreditCode" : "S",
"FiscalYearVariant" : "K4"
}
]
}
}
Check ACDOCP table with the same input. You will see that additional row is added that is 100 EUR. This is because ACDOCP register the difference and change history. If you sum the 2 rows, it will be 600 EUR.

Same thing if we decided that planing amount will be 300 EUR instead. So we will post with exact same data except amount = 300 EUR.
"AmountInCompanyCodeCurrency" : "300.00",
Difference of – 300 EUR is added as new row and if you sum them up, it will always be the amount entered on the latest posting.

4. Calling S/4 OData service from ABAP
Now having understood how this Odata API works, let’s integrate this into an ABAP code. You can choose to trigger this OData from inside of S/4 or from Business Technology Platform. In this blog, we will show you an example where ABAP program in S/4 triggers this API to make update in ACDOCP.
here is the code:
REPORT yacdocp_update.
START-OF-SELECTION.
DATA: lv_xbody TYPE xstring,
lv_request_xbody TYPE xstring,
lv_request_json_data TYPE string,
lv_json_data TYPE string,
lv_response_full_string TYPE string.
"Create data
lv_request_json_data = '{' &&
' "PlanDataAggrgnLvlFieldsString" : "Ledger,PlanningCategory,LedgerFiscalYear,GLAccount,CompanyCode,AmountInCompanyCodeCurrency,CompanyCodeCurrency",' &&
' "PlanDataRplcScopeFieldsString" : "Ledger,PlanningCategory,LedgerFiscalYear,GLAccount,CompanyCode,AmountInCompanyCodeCurrency",' &&
' "to_FinPlanningEntryItemTP" : {' &&
' "results" : [' &&
' {' &&
' "ID" : "PLANTEST1",' &&
' "Ledger" : "0L",' &&
' "PlanningCategory" : "PLAN",' &&
' "LedgerFiscalYear" : "2022",' &&
' "GLAccount" : "0001140000",' &&
' "CompanyCode" : "C000",' &&
' "ControllingArea" : "1000",' &&
' "CompanyCodeCurrency" : "EUR",' &&
' "AmountInCompanyCodeCurrency" : "1500.00",' &&
' "ChartOfAccounts" : "CH00",' &&
' "ControllingDebitCreditCode" : "S",' &&
' "FiscalYearVariant" : "K4"' &&
' }' &&
' ]' &&
' }' &&
'}'
.
lv_request_xbody = /iwfnd/cl_sutil_xml_helper=>transform_to_xstring( lv_request_json_data ).
/iwfnd/cl_sutil_client_proxy=>get_instance( )->web_request(
EXPORTING
it_request_header = VALUE /iwfnd/sutil_property_t(
(
name = if_http_header_fields_sap=>request_method
value = if_http_entity=>co_request_method_post
) (
name = if_http_header_fields_sap=>request_uri
value = |/sap/opu/odata/sap/API_FINPLANNINGDATA_SRV/FinancialPlanData|
) (
name = if_http_header_fields=>accept
value = |application/json|
) (
name = if_http_header_fields=>content_type
value = |application/json|
)
)
iv_request_body = lv_request_xbody
IMPORTING
ev_status_code = DATA(lv_status_code)
ev_response_body = lv_xbody
ev_error_text = DATA(lv_error_text)
).
lv_response_full_string = /iwfnd/cl_sutil_xml_helper=>transform_to_string( lv_xbody ).
For the code breakdown:
- class /iwfnd/cl_sutil_client_proxy is the client that allows you to reuse Odata service that resides in S/4 system. To trigger an web request, method ‘web_request’ is used.
- In the export parameter it_request_header, we map the request header information such as method Post, Request URI, and JSON as accepted content type
- In the iv_request_body parameter, we are passing actual data.
- In the Importing parameter, response information are returned.
Everything is exactly as it was demonstrated in the SAP Gateway Client in the previous section.
If you run this code with the right data for your system, you should be able to see a new row generated for your ACDOCP table.
Check out other blogs for extending SAP S/4 HANA from SAP Extensibility 101.