[[{“value”:”
Consider there is a single RAP BO Action holding a massive and complex material master processing logic. This action handles everything from material headers and plant associations to sales delivery data and error logging.Here the Challenge is such different external systems need to use this exact same logic through the RAP API. However, they do not want or need to send the entire massive payload required by the metadata.
- Reusability: One base class handles the core logic. Multiple projections handle unique business processes.
- Customized I/O: Projections only expose the fields they need.
- No Payload Tension: Callers aren’t forced to send dummy data for mandatory decimal or date fields they don’t use.
- Customized Field namings: We can map internal SAP field names to modern, external API-friendly names.
So is Action Parameter Redefining is a part of RAP Extensions?
No, RAP Parameter Redefining does not comes under RAP Extensions directly, since both the methods have distinct limitations.
- Parameter Redefinition (BO Layering): This is a top-down narrowing approach. A wide or complex parameter at the base layer is created and simple/Business specific projection or interface layer is created on top of it to expose a limited field data to a particular UI application or OData service. Here inorder to create redefinition of base, the required fields at projection should first exist in base. If not first base level abstract entity should be extended and respective fields should be added. Post that it can be consumed at Projection/Interface layer.
- RAP Extensibility (BDEF Extensions): This is a bottom-up expanding approach. This will be used when requirement comes to add new fields, validations etc to standard SAP Business Object without modifying the original code.
So in simple terms if you want to add new fields to existing BDef by standard then go with RAP Extensions and if you want to use limited fields from Base Action to handle another Business scenario then use RAP Parameter Redefinitions.
Prerequisites
- So, lets take Material Master standard API – API_PRODUCT as reference. We are gonna recreate same modelling in RAP with Redefined Action Parameters. Note: I will not do each validations and code level reimplementation of Material master standard API here, instead i will perform the same Modelling using RAP.
- The BASE Layer in our Modelling will be a Consumption CDS View which will have a Behavior Definition which has one ACTION which handles CRUD Operation of Material Master.
- Why Action ? – We can achieve this through CREATE Method as well. But keep the Fiori app mindset aside and think from Interface perspective. If an third party system API triggers backend SAP System to do MM Activities, they will expect response as well. so Create have Failed/Reported parameters which supports only failure or success pop up kind of OData header response, but it wont support OData body level response and Deep responses.
- To achieve this we must use Deep Action. So the BASE Layer should be built on top of consumption view and in Behaviour Definition of consumption view, An Action would be create which will have Deep Input and Response of Material Master which includes all fields.
- On top of Base view, First Projection view will be created and for our business use case lets consider – Business Scenario-1 is trying to reuse the Material Master Base API’s Action by using redefined parameters with limited field from base. ( i.e. in OData based approach we generally do OData redefinition / extension for such use case. but still it has its own limitations of standard validations over fields etc. But with the custom RAP Approach, its complete dynamic design and end user would have control over everything ).
- On top of Base view Second Projection view will be created again for Business scenario -2 .
- Both Business scenario 1 and 2 will try to reuse limited fields from BASE for their use case with their respective namings for input and response payload.
- All the Base, Business scenario-1 & 2 actions parameters should have respective abstract behaviour definitions and mapping should be done at each level redirecting to BASE Behaviour definition.
Design Architecture:
Please go through this flow chart carefully which explains the complete design architecture for our implementation.
Lets start development…!
BASE LAYER – GENERIC COMPLETE PAYLOAD
1. The CDS Consumption view ZC_MATERIAL_MASTER_GENERIC is created with exposure of only one key field. Since our business logic is packed in RAP Action, This layer acts as dummy layer which helps to create behaviour definition on top of it.
Note: Whatever artifacts are created, please use the term extensible wherever possible, so that other business user can able to extend your artifacts and add additional fields if required. This is the practice followed for dynamic API approach.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Material Master Base view’
@Metadata.ignorePropagatedAnnotations: true
@VDM.viewType: #CONSUMPTION
@AbapCatalog.extensibility.extensible: true
define root view entity ZC_MATERIAL_MASTER_GENERIC as select from A_Product
{
key cast(Product as matnr preserving type ) as Material
}
2. The Behaviour Definition for Consumption view ZC_MATERIAL_MASTER_GENERIC is created on top of CDS View. In the consumption view Deep Action is defined.
unmanaged implementation in class zcl_material_master_base unique;
strict ( 2 );
extensible;
define behavior for ZC_MATERIAL_MASTER_GENERIC alias MaterialAPI
lock master
authorization master ( instance ) extensible
{
action l_material_api deep parameter ZA_MATERIAL_API_PARAMETER deep result [0..*] ZA_MATERIAL_API_PARAMETER;
create;
field ( readonly ) Material;
}
3. Now let’s start with Abstract Entity creation for Material Master. Since Material Master uses API_PRODUCT standard API, We will take reference of the same. API_PRODUCT constructs payload from SADL CDS Reference A_PRODUCT. From A_PRODUCT multiple deep structure goes to multiple view components of MM. Like Basic view, Plant view etc. As part of this blog we are not going to replicate complete MM. If we do so then blog will go very lengthy. so the aim is to expose large set of data in Base view from Material master and use some limited data from those large set in Projection layer redefinitions.
- Agreeing to that, Our first Abstract entity for BASE layer is as below.
@EndUserText.label: ‘Parameter for API’
@AbapCatalog.extensibility.extensible: true
define root abstract entity ZA_MATERIAL_API_PARAMETER
{
key Product : matnr;
Id : abap.char( 10 );
@EndUserText.label:’Acknowledgement Code’
AckCode : abap.char(10);
@EndUserText.label:’Status’
Status : abap.char(1);
_MaterialData : composition [1..1] of za_material_base_input;
_log : composition [1..*] of za_material_error_log;
}
ZA_MATERIAL_BASE_PARAMETER is header and it has component called ID. Using that ID only we are going to identify which business scenario is triggering. For example: ID = BP-01 means first Business process/Projection layer and BP-BASE means base layer is triggering.
- ZA_MATERIAL_BASE_INPUT is child layer which has all MARA /A_PRODUCT entries.
@EndUserText.label: ‘Abstract base for material’
@AbapCatalog.extensibility.extensible: true
define abstract entity za_material_base_input
{
key Product : matnr;
ProductType : mtart;
CrossPlantStatus : mstae;
CrossPlantStatusValidityDate : mstde;
CreationDate : ersda;
CreatedByUser : ernam;
LastChangeDate : laeda;
LastChangedByUser : aenam;
IsMarkedForDeletion : lvorm;
ProductOldID : bismt;
@Semantics.quantity.unitOfMeasure: ‘WeightUnit’
GrossWeight : brgew;
PurchaseOrderQuantityUnit : bstme;
SourceOfSupply : bwscl;
WeightUnit : gewei;
@Semantics.quantity.unitOfMeasure: ‘WeightUnit’
NetWeight : ntgew;
CountryOfOrigin : herkl;
CompetitorID : wettb;
ProductGroup : matkl;
BaseUnit : meins;
ItemCategoryGroup : mtpos_mara;
ProductHierarchy : prodh_d;
Division : spart;
VarblPurOrdUnitIsActive : vabme;
VolumeUnit : voleh;
@Semantics.quantity.unitOfMeasure: ‘VolumeUnit’
MaterialVolume : volum;
ANPCode : j_1banp;
Brand : wrf_brand_id;
ProcurementRule : bwvor;
ValidityStartDate : datab;
LowLevelCode : disst;
ProdNoInGenProdInPrepackProd : wstr_satnr_pp;
SerialIdentifierAssgmtProfile : serlv;
SizeOrDimensionText : groes;
IndustryStandardName : normt;
ProductStandardID : ean11;
InternationalArticleNumberCat : numtp;
ProductIsConfigurable : kzkfg;
IsBatchManagementRequired : xchpf;
ExternalProductGroup : extwg;
CrossPlantConfigurableProduct : satnr;
SerialNoExplicitnessLevel : serlv;
ProductManufacturerNumber : mfrpn;
ManufacturerNumber : mfrnr;
ManufacturerPartProfile : mprof;
QltyMgmtInProcmtIsActive : qmpur;
IndustrySector : mbrsh;
HandlingIndicator : cifhdlcode;
WarehouseProductGroup : cifwhmatgr;
WarehouseStorageCondition : cifwhstc;
StandardHandlingUnitType : cifhutypdf;
SerialNumberProfile : serlv;
AdjustmentProfile : cifadprof;
PreferredUnitOfMeasure : /scwm/de_puom;
IsPilferable : cifpilfrbl;
IsRelevantForHzdsSubstances : cifhazmat;
@Semantics.quantity.unitOfMeasure : ‘TimeUnitForQuarantinePeriod’
QuarantinePeriod : cifqqtime;
TimeUnitForQuarantinePeriod : cifqqtimeuom;
QualityInspectionGroup : cifqgrp;
AuthorizationGroup : begru;
DocumentIsCreatedByCAD : cadkz;
HandlingUnitType : cifhutypdf;
HasVariableTareWeight : ciftarevar;
@Semantics.quantity.unitOfMeasure: ‘UnitForMaxPackagingDimensions’
MaximumPackagingLength : cifmaxl;
@Semantics.quantity.unitOfMeasure: ‘UnitForMaxPackagingDimensions’
MaximumPackagingWidth : cifmaxb;
@Semantics.quantity.unitOfMeasure: ‘UnitForMaxPackagingDimensions’
MaximumPackagingHeight : cifmaxh;
UnitForMaxPackagingDimensions : cifmaxduom;
_MaterialData : association to parent ZA_MATERIAL_API_PARAMETER on $projection.Product = _MaterialData.Product;
_Plant : composition [0..*] of ZA_PRODUCT_PLANT;
_SalesDelivery : composition [0..*] of ZA_PRODUCT_SALES_DELIVERY;
_ProductSalesTax : composition [0..*] of ZA_PRODUCT_SALES_TAX;
// _Description : composition [0..*] of A_ProductDescription;
// _ProductType : composition [1..1] of A_ProductType;
// _ProductGroup : composition [0..1] of A_ProductGroup;
// _ProductBasicText : composition [0..*] of A_ProductBasicText;
// _ProductPurchaseText : composition [0..*] of A_ProductPurchaseText;
// _ProductInspectionText : composition [0..*] of A_ProductInspectionText;
// _ProductUnitsOfMeasure : composition [0..*] of A_ProductUnitsOfMeasure;
// _ProductStorage : composition [0..1] of A_ProductStorage;
// _ProductSales : composition [0..1] of A_ProductSales;
// _ProductQualityMgmt : composition [0..1] of A_ProductQualityMgmt;
// _ProductProcurement : composition [0..1] of A_ProductProcurement;
// _Valuation : composition [0..*] of A_ProductValuation;
}
As i said already, Im creating this MM replica RAP BO with limited associations only and henceforth I have commented the associations that im not creating. I refered those commented associations with standard CDS View names to create it for future.
- ZA_PRODUCTPLANT is subchild layer which holds all field reference from A_PRODUCTPLANT.
@EndUserText.label: ‘Product Plant Base’
@AbapCatalog.extensibility.extensible: true
define abstract entity ZA_PRODUCT_PLANT
{
key Product : matnr;
key Plant : werks_d;
PurchasingGroup : ekgrp;
CountryOfOrigin : herkl;
RegionOfOrigin : herkr;
ProductionInvtryManagedLoc : lgpro;
ProfileCode : dispr;
ProfileValidityStartDate : dats;
AvailabilityCheckType : mtvfp;
FiscalYearVariant : periv;
PeriodType : perkz;
ProfitCenter : prctr;
Commodity : stawn;
GoodsReceiptDuration : webaz;
MaintenanceStatusName : pstat_d;
IsMarkedForDeletion : lvowk;
MRPType : dismm;
MRPResponsible : dispo;
ABCIndicator : maabc;
@Semantics.quantity.unitOfMeasure: ‘BaseUnit’
MinimumLotSizeQuantity : bstmi;
@Semantics.quantity.unitOfMeasure: ‘BaseUnit’
MaximumLotSizeQuantity : bstma;
@Semantics.quantity.unitOfMeasure: ‘BaseUnit’
FixedLotSizeQuantity : bstfe;
ConsumptionTaxCtrlCode : steuc;
IsCoProduct : kzkup;
ProductIsConfigurable : xfeld;
StockDeterminationGroup : bf_group;
@Semantics.quantity.unitOfMeasure: ‘BaseUnit’
StockInTransferQuantity : umlme;
@Semantics.quantity.unitOfMeasure: ‘BaseUnit’
StockInTransitQuantity : trame;
HasPostToInspectionStock : insmk_mat;
IsBatchManagementRequired : xchpf;
SerialNumberProfile : serail;
IsNegativeStockAllowed : xmcng;
@Semantics.quantity.unitOfMeasure: ‘BaseUnit’
GoodsReceiptBlockedStockQty : bwesb;
HasConsignmentCtrl : xfeld;
FiscalYearCurrentPeriod : gjahr;
FiscalMonthCurrentPeriod : monat;
ProcurementType : beskz;
IsInternalBatchManaged : xchar;
ProductCFOPCategory : char4;
ProductIsExciseTaxRelevant : xfeld;
BaseUnit : meins;
ConfigurableProduct : stdpd;
GoodsIssueUnit : ausme;
MaterialFreightGroup : ladgr;
OriginalBatchReferenceMaterial : vrbmt;
OriglBatchManagementIsRequired : xfeld;
ProductIsCriticalPrt : kzkri;
ProductLogisticsHandlingGroup : loggr;
_Plant : association to parent za_material_base_input on $projection.Product = _Plant.Product;
_StorageLocation : composition [0..*] of ZA_PLANT_SLOC;
// _ProductPlantProcurement : composition [0..1] of A_ProductPlantProcurement;
// _ProductWorkScheduling : composition [0..1] of A_ProductWorkScheduling
// _ProductSupplyPlanning : composition [0..1] of A_ProductSupplyPlanning;
// _ProdPlantInternationalTrade : composition [0..1] of A_ProdPlantInternationalTrade;
// _ProductPlantCosting : composition [0..1] of A_ProductPlantCosting;
// _ProductPlantForecast : composition [0..1] of A_ProductPlantForecast;
// _PlantQualityMgmt : composition [0..1] of A_PlantQualityMgmt;
// _PlantSales : composition [0..1] of A_PlantSales;
// _PlantStorage : composition [0..1] of A_PlantStorage;
// _PlantText : composition [0..1] of A_PlantText;
// _PlantMRPArea : composition [0..*] of A_PlantMRPArea;
}
Here also Im extending further to storage location association only, other associations I have commented for future usage.
- ZA_PLANT_SLOC is super child extending from child for storage location MARD/A_PRODUCTSTORAGELOCATION reference fields.
@EndUserText.label: ‘Plant Storage location’
@AbapCatalog.extensibility.extensible: true
define abstract entity ZA_PLANT_SLOC
{
key Product : matnr;
key Plant : werks_d;
key StorageLocation : lgort_d;
WarehouseStorageBin : lgpbe;
MaintenanceStatus : pstat_d;
PhysicalInventoryBlockInd : sperr;
CreationDate : ersda;
IsMarkedForDeletion : lvolg;
DateOfLastPostedCntUnRstrcdStk : dats;
InventoryCorrectionFactor : bskrf;
InvtryRestrictedUseStockInd : kzile;
InvtryCurrentYearStockInd : kzill;
InvtryQualInspCurrentYrStkInd : kzilq;
InventoryBlockStockInd : kzils;
InvtryRestStockPrevPeriodInd : kzvle;
InventoryStockPrevPeriod : kzvll;
InvtryStockQltyInspPrevPeriod : kzvlq;
HasInvtryBlockStockPrevPeriod : kzvls;
FiscalYearCurrentPeriod : gjahr;
FiscalMonthCurrentPeriod : monat;
FiscalYearCurrentInvtryPeriod : gjahr;
LeanWrhsManagementPickingArea : mard_kober;
_StorageLocation : association to parent ZA_PRODUCT_PLANT on $projection.Product = _StorageLocation.Product
and $projection.Plant = _StorageLocation.Plant;
}
- ZA_PRODUCT_SALES_DELIVERY is another sub-child entity from child ZA_MATERIAL_BASE_INPUT. It Holds field reference from standard view A_ProductSalesDelivery/MVKE.
@EndUserText.label: ‘Product Sales Delivery’
@AbapCatalog.extensibility.extensible: true
define abstract entity ZA_PRODUCT_SALES_DELIVERY
{
key Product : matnr;
key ProductSalesOrg : vkorg;
key ProductDistributionChnl : vtweg;
@Semantics.quantity.unitOfMeasure: ‘BaseUnit’
MinimumOrderQuantity : minau;
SupplyingPlant : dwerk_ext;
PriceSpecificationProductGroup : kondm;
AccountDetnProductGroup : ktgrm;
@Semantics.quantity.unitOfMeasure: ‘BaseUnit’
DeliveryNoteProcMinDelivQty : minlf;
ItemCategoryGroup : mtpos;
@Semantics.unitOfMeasure: true
DeliveryQuantityUnit : schme;
@Semantics.quantity.unitOfMeasure: ‘DeliveryQuantityUnit’
DeliveryQuantity : scmng;
ProductSalesStatus : vmsta;
ProductSalesStatusValidityDate : vmstd;
SalesMeasureUnit : vrkme;
IsMarkedForDeletion : lvorm;
ProductHierarchy : prodh_d;
FirstSalesSpecProductGroup : mvgr1;
SecondSalesSpecProductGroup : mvgr2;
ThirdSalesSpecProductGroup : mvgr3;
FourthSalesSpecProductGroup : mvgr4;
FifthSalesSpecProductGroup : mvgr5;
@Semantics.quantity.unitOfMeasure: ‘BaseUnit’
MinimumMakeToOrderOrderQty : efmng;
BaseUnit : meins;
LogisticsStatisticsGroup : versg;
VolumeRebateGroup : bonus;
ProductCommissionGroup : provg;
CashDiscountIsDeductible : sktof;
PricingReferenceProduct : pmatn;
RoundingProfile : rdprf;
ProductUnitGroup : megru;
VariableSalesUnitIsNotAllowed : vavme;
ProductHasAttributeID01 : prat1;
ProductHasAttributeID02 : prat2;
ProductHasAttributeID03 : prat3;
ProductHasAttributeID04 : prat4;
ProductHasAttributeID05 : prat5;
ProductHasAttributeID06 : prat6;
ProductHasAttributeID07 : prat7;
ProductHasAttributeID08 : prat8;
ProductHasAttributeID09 : prat9;
ProductHasAttributeID10 : prata;
//”—- Associations (navigation only, no ON) —-
_SalesDelivery : association to parent za_material_base_input on $projection.Product = _SalesDelivery.Product;
// _SalesText : composition [0..*] of A_ProductSalesText;
// _SalesTax : composition [0..*] of A_ProductSalesTax;
}
- ZA_PRODUCT_SALES_TAX is another sub-child entity from child ZA_MATERIAL_BASE_INPUT. It Holds field reference from standard view A_ProductSalesTax/MLAN.
@EndUserText.label: ‘Product Sales Tax’
@AbapCatalog.extensibility.extensible: true
define abstract entity ZA_PRODUCT_SALES_TAX
{
key Product : productnumber;
key Country : aland;
key TaxCategory : tatyp;
key TaxClassification : taxm1;
_ProductSalesTax : association to parent za_material_base_input on $projection.Product = _ProductSalesTax.Product;
}
- ZA_MATERIAL_ERROR_LOG is another child component of Parent Abstract Entity ZA_MATERIAL_API_PARAMETER. It holds error log data which needs to be send as response.
@EndUserText.label: ‘Error log’
@AbapCatalog.extensibility.extensible: true
define abstract entity za_material_error_log
{
@EndUserText.label:’Unique Identifier’
key Product : abap.char(12);
@EndUserText.label:’Item Number’
key MessageCode : abap.char(24);
@EndUserText.label : ‘Error Message’
key Message : abap.char(255);
_log : association to parent ZA_MATERIAL_API_PARAMETER on $projection.Product = _log.Product;
}
Now we have all abstract entities ready for BASE layer. Let’s build a final Abstract Behaviour Definition for the Root Abstract Entity.
- Abstract Behaviour Definition for ZA_MATERIAL_API_PARAMETER is created and here the associations on each layer should be exposed correctly as below.
abstract;
strict ( 2 );
with hierarchy;
define behavior for ZA_MATERIAL_API_PARAMETER
{
association _MaterialData;
association _log;
}
define behavior for za_material_error_log
{
}
define behavior for za_material_base_input
{
association _Plant;
association _SalesDelivery;
association _ProductSalesTax;
}
define behavior for ZA_PRODUCT_PLANT
{
association _StorageLocation;
}
define behavior for ZA_PLANT_SLOC
{
}
define behavior for ZA_PRODUCT_SALES_DELIVERY
{
}
define behavior for ZA_PRODUCT_SALES_TAX
{
}
PROJECTION LAYER – FOR BUSINESS SCENARIO – 1:
1. The CDS Projection View 1 – ZP_MATERIAL_MASTER_BS1 is created by exposing same one field as base layer.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Material Master: Business Scenario – 1’
@Metadata.ignorePropagatedAnnotations: true
@AbapCatalog.extensibility.extensible: true
define root view entity ZP_MATERIAL_MASTER_BS1
as projection on ZC_MATERIAL_MASTER_GENERIC
{
key Material
}
2. Projection Behaviour Definition for ZP_MATERIAL_MASTER_BS1 is built as below by reusing same action with different parameter defnition.
projection;
strict ( 2 );
extensible;
define behavior for ZP_MATERIAL_MASTER_BS1 alias MaterialAPIInt_1
extensible
{
use create;
use action l_material_api deep parameter za_mat_param_bs1 deep result za_material_param_bs1;
}
3. Now let’s start with Abstract Entity creation for Projection layer which handles Business Scenario – 1. Since Majority of the MM fields are exposed in BASE Layer, in this layer we are going to consume limited fields from it. We may skip some fields/associations from Base view completely and select the records only which business scenario-1 demands. We will do some renaming of field names also here and do mapping at behaviour level.
- Agreeing to that, Our first Abstract entity for BS-1 layer is as below.
@EndUserText.label: ‘Input for interface 1’
@AbapCatalog.extensibility.extensible: true
define root abstract entity za_mat_param_bs1
{
key Product : matnr;
Id : abap.char( 10 );
AckCode : abap.char(10);
Status : abap.char(1);
_MaterialData : composition [1..1] of za_material_bs1_input;
_log : composition [0..*] of za_bs1_error_log;
}
Here we have all the fields in BS-1 Header abstract same as Base Header Abstract. Even the associations are also as-is base.
- ZA_MATERIAL_BS1_INPUT is first child Abstract entity of Base ZA_MAT_PARAM_BS1. Same as base, it holds MARA/A_PRODUCT data.
@EndUserText.label: ‘interface input 2’
@AbapCatalog.extensibility.extensible: true
define abstract entity za_material_bs1_input
{
key Product : matnr;
_MaterialData : association to parent za_mat_param_bs1 on $projection.Product = _MaterialData.Product;
_Plant : composition [0..*] of za_bs1_product_plant;
_SalesDelivery : composition [0..*] of za_bs1_product_sales_dlv;
}
Here the drastic difference comes, From ZA_MATERIAL_BS1_INPUT, the business scenario demands only Product field whereas in BASE we do have all MARA Fields exposed as template. so here we are using only Product.
- ZA_BS1_PRODUCT_PLANT is Subchild Abstract entity of Base ZA_MATERIAL_BS1_INPUT. Same as base, it holds MARC/A_PRODUCTPLANT data.
@EndUserText.label: ‘interface input 3’
@AbapCatalog.extensibility.extensible: true
define abstract entity ZA_BS1_PRODUCT_PLANT
{
key Product : matnr;
key Plant : werks_d;
_Plant : association to parent za_material_bs1_input on $projection.Product = _Plant.Product;
}
Here also the Business scenario – 1 demands only Plant from MARC/A_PRODUCTPLANT where in Base all fields are exposed. Also noticeably storagelocation association is also not used here.
- ZA_BS1_PRODUCT_SALES_DLV is another subchild of ZA_MAT_PARAM_BS1. Same as base it holds field reference from standard view A_ProductSalesDelivery/MVKE.
@EndUserText.label: ‘Product Sales Delivery’
@AbapCatalog.extensibility.extensible: true
define abstract entity ZA_BS1_PRODUCT_SALES_DLV
{
key Product : matnr;
key ProductSalesOrg : vkorg;
key ProductDistributionChnl : vtweg;
Group1 : mvgr1;
_SalesDelivery : association to parent za_material_bs1_input on $projection.Product = _SalesDelivery.Product;
}
Here also limited fields are considered from Base. And important thing to notice. in base there is no field called Group1. As I mentioned earlier, I renamed “Group1” at Projection which is defined as “FirstSalesSpecProductGroup” in Base. So in Action class even if user passes value in Group1 in payload the value reflects to FirstSalesSpecProductGroup.
- ZA_BS1_ERROR_LOG is another child of parent Abstract ZA_MAT_PARAM_BS1.
@EndUserText.label: ‘Error log for int1’
@AbapCatalog.extensibility.extensible: true
define abstract entity ZA_BS1_ERROR_LOG
{
@EndUserText.label:’Unique Identifier’
key Product : matnr;
@EndUserText.label:’Item Number’
key ErrorCode : abap.char(24);
@EndUserText.label : ‘Error Message’
ErrorMsg : abap.char(255);
_log : association to parent za_mat_param_bs1 on $projection.Product = _log.Product;
}
Here also “MessageCode” and “Message” from Base Abstract is renamed as “ErrorCode” and “ErrorMsg”.
- Now we have all abstract entities ready for PROJECTION layer. Let’s build a final Abstract Behaviour Definition for the Root Abstract Entity.
- Abstract Behaviour Definition for ZA_MAT_PARAM_BS1 is created and here the associations on each layer should be exposed correctly as below.
- Here comes the important part which is MAPPING. Compared to BASE level, we have not considered many fields in PROJECTION-1 for BS-1 and we renamed some fields at SalesDelivery level and Log level based on BS-1 Requirements. To ensure the seamless flow of data happens without any error, we should do mapping at PROJECTION ABSTRACT BDEF level redirecting to BASE ABSTRACT BDEF.
abstract;
strict ( 2 );
with hierarchy;
define behavior for za_mat_param_bs1
{
association _MaterialData;
association _log;
deep mapping for za_material_api_parameter corresponding {
Id = Id;
Product = Product;
AckCode = AckCode;
Status = Status;
sub _MaterialData = _MaterialData;
sub _log = _log;
}
}
define behavior for za_bs1_error_log
{
deep mapping for za_material_error_log corresponding
{
Product = Product;
ErrorCode = MessageCode;
ErrorMsg = Message;
}
}
define behavior for za_material_bs1_input
{
association _Plant;
association _SalesDelivery;
deep mapping for za_material_base_input corresponding
{
Product = Product;
sub _Plant = _Plant;
sub _SalesDelivery = _SalesDelivery;
}
}
define behavior for za_bs1_product_plant
{
deep mapping for za_product_plant corresponding
{
Product = Product;
Plant = Plant;
}
}
define behavior for za_bs1_product_sales_dlv
{
deep mapping for za_product_sales_delivery corresponding
{
Product = Product;
ProductSalesOrg = ProductSalesOrg;
ProductDistributionChnl = ProductDistributionChnl;
Group1 = FirstSalesSpecProductGroup;
}
}
Major things to notice in PROJECTION-1 Abstract entity BDef:
- Here mapping is done at each association level redirecting to parent entity. This mapping should be done manually and it wont be autogenerated.
- The renamed Group1, ErrorCode, ErrMsg is correctly mapped to base target fields.
- Same like fields, associations also should be mapped using the syntax “SUB” . “CORRESPONDING” is mandatory to make sure associations are set perfectly at each level.
PROJECTION LAYER – FOR BUSINESS SCENARIO – 2:
1. The CDS Projection View 2 – ZP_MATERIAL_MASTER_BS2 is created by exposing same one field as base layer.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Material Master: Business Scenario – 2’
@Metadata.ignorePropagatedAnnotations: true
@AbapCatalog.extensibility.extensible: true
define root view entity ZP_MATERIAL_MASTER_BS2
as projection on ZC_MATERIAL_MASTER_GENERIC
{
key Material
}
2. Behaviour Definition for above Projection Layer for Business scenario – 2 is created as below.
projection;
strict ( 2 );
extensible;
define behavior for ZP_MATERIAL_MASTER_BS2 extensible
{
use create;
use action l_material_api deep parameter za_mat_param_bs2 deep result za_mat_param_bs2;
use action l_validate_data;
}
3. Now, same as BS-1 Abstract Entities, BS2 Abstract Entities also will use limited fields from BASE Entity as per Business scenario – 2.
- ZA_MAT_PARAM_BS2 is the header root abstract entity of Behavior Action. It is created as below.
@EndUserText.label: ‘Int 2 abs’
define root abstract entity ZA_MAT_PARAM_BS2
{
key Product : matnr;
Id : abap.char( 10 );
_log : composition [0..*] of za_bs2_error_log
}
Here, if you notice there is not even Material Extension association itself is defined. so user require only Material as input and Error log as output. _Material association is skipped in Business scenario 2. Also, Fields AckCode and Status is not considered as part of Business Scenario – 2.
- ZA_BS2_ERROR_LOG is the only child association of ZA_MAT_PARAM_BS2 and it holds error log data.
@EndUserText.label: ‘int 2 log’
define abstract entity za_bs2_error_log
{
@EndUserText.label:’Unique Identifier’
key Product : matnr;
@EndUserText.label:’Item Number’
key ErrorCode : abap.char(24);
key ErrorMessage : abap.char(255);
_log : association to parent za_mat_param_bs2 on $projection.Product = _log.Product;
}
In this log as well, same as BS1 – Messagecode and Message is renamed ErrorCode and ErrorMessage. The respective mapping will be done in Abstract BDef.
- Abstract Behaviour Definition for Root Abstract entity ZA_MAT_PARAM_BS2 is created as below.
abstract;
strict ( 2 );
with hierarchy;
define behavior for za_mat_param_bs2
{
association _log;
deep mapping for za_material_api_parameter corresponding {
Id = Id;
Product = Product;
sub _log = _log;
}
}
define behavior for za_bs2_error_log
{
deep mapping for za_material_error_log
{
Product = Product;
ErrorCode = MessageCode;
ErrorMessage = Message;
}
}
Major things to notice in PROJECTION-2 Abstract entity BDef:
- Here mapping is done at one association level redirecting to parent entity. This mapping should be done manually and it wont be autogenerated.
- The renamed ErrorCode, ErrMsg is correctly mapped to base target fields.
- Same like fields, associations also should be mapped using the syntax “SUB” . “CORRESPONDING” is mandatory to make sure associations are set perfectly at each level.
Behavior Implementation class – Action method which handles payload logic for BASE, BS-01 & BS-02 Projection.
- In Action method l_Material_api, Im not implementing select queries/Validations/BAPI call since the intention of this blog is not to explain how to build logic but to explain how to enable rightful framework. For logic overview, BAPI can be called in Saver class of RAP and select queries and validations can be build in RAP Action/Function.
METHOD l_material_api.
IF keys is NOT INITIAL.
DATA(lt_keys) = keys.
DATA(ls_key) = VALUE #( lt_keys[ 1 ] OPTIONAL ).
result = VALUE #( (
%cid_ref = ls_key-%cid_ref
%pky = ls_key-%pky
%tky = ls_key-%tky
%key = ls_key-%key
material = ls_key-material
%param-id = ls_key-%param-id
%param-ackcode = ls_key-%param-Id
%param-status = ‘S’
%param-product = ‘001’
%param-_log = VALUE #( ( product = ‘001’
messagecode = ‘NA’
message = ‘exe’ ) )
%param-_materialdata = VALUE #( product = ‘001’
anpcode = ‘1’
AdjustmentProfile = ‘1’
BaseUnit = ‘EA’
_plant = VALUE #( ( Product = ‘001’ Plant = ‘002’ Commodity = ‘X’ ) )
_salesdelivery = VALUE #( ( product = ‘001’
productsalesorg = ‘002’
productdistributionchnl = ’10’
firstsalesspecproductgroup = ‘MM’
DeliveryQuantityUnit = ‘EA’ ) ) )
) ).
ENDIF.
ENDMETHOD.
- As per the above logic, mapping is done to all the base level fields in result parameter, but still based on individual response mapping at each abstract behaviour definition of BS-01 & 2, response will be shown in output. Not all the mappings will be considered.
- Also, as per the naming given in BS-01 & BS-02 Abstract entities, the mapping will happen. If Abstract entity mapping is done correctly to Product in Base Entity with MaterialCode in Projection entity. Then if user pass payload with MaterialCode as input JSON then it reflects to Product in Action class. That is the beauty of Parameter Redefinition with mapping.
- This is how this implementation satisfies one RAP Action with Infinite APIs redefinition.
SERVICE DEFINITION:
@EndUserText.label: ‘Service Definition for Material API’
define service ZMAT_API_SERV_DEF {
expose ZC_MATERIAL_MASTER_GENERIC;
expose ZP_MATERIAL_MASTER_BS1;
expose ZP_MATERIAL_MASTER_BS2;
}
SERVICE BINDING:
- Create V4 Service Binding – ZSB_MM_MAT_GENERIC_PAYLOAD and publish it’s service group in Tcode – /n/iwfnd/v4_admin.
TESTING:
- Now we are not building Fiori App on top of this service Binding, since this API is tuned as interface build which can be reimplemented multiple times as per Business requirements.
- So let’s test the service from OData Gateway – /n/iwfnd/v4_admin.
- We have three entities exposed in service Definition now. One Base Entity and Two Projection Entities. Base will hold MM payload as template and Projection Entities hold limited fields from Base.
- So lets test all three one by one.
- All three entities can be executed seperately from same API which will redirect to same BASE Action. So, while framing URL and payload we should use distinct URL and payload for each execution.
- schemanamespace and action name will be same.
- To know how to form V4 URL for executing RAP Action, Please refer to my existing blog – RAP Deep Action with OData V4
TESTING BASE:
- Since Base holds all fields from MM, Our JSON should have all values mapped to the fields that we exposed in Abstract entities. It’s not mandatory to pass all association fields of Item Abstract entities, but if single field from Header or Header direct association is/are missed then execution wont happen and it leads to OData error.(i.e. To overcome this only we did Parameter redefinition where user can have customized payload configuration).
- All the entities point towards same action logic, we will identify the response based on the response structure designed in each Abstract entity.
- In our Input JSON for Base, Im passing all the header fields and it’s association without fail, but in item and subchild abstract associations – Im passing limited fields only.
- INPUT URL:
- /sap/opu/odata4/sap/zsb_mm_mat_generic_payload/srvd/sap/zmat_api_serv_def/0001/ZC_MATERIAL_MASTER_GENERIC(‘ ‘)/
- {{ Schema namespace in metadata->starts with com.sap.gateway…}}
- .l_material_api
So input URL will be 1 + 2 + 3.
- INPUT JSON:
{
“Product”: “MAT-ABCD”,
“Id”: “BP-BASE”,
“AckCode”: “TEST”,
“Status”: “S”,
“_MaterialData”: {
“Product”: “MAT-ABCD”,
“ProductType”: “CDEF”,
“CrossPlantStatus”: “01”,
“CrossPlantStatusValidityDate”: “2026-12-31”,
“CreationDate”: “2026-07-31”,
“CreatedByUser”: “SAP_USER”,
“LastChangeDate”: “2026-07-31”,
“LastChangedByUser”: “AI_AGENT”,
“IsMarkedForDeletion”: true,
“ProductOldID”: “OLD-MAT-99”,
“GrossWeight”: 150.5,
“PurchaseOrderQuantityUnit”: “PC”,
“SourceOfSupply”: “F”,
“WeightUnit”: “KG”,
“NetWeight”: 142.5,
“CountryOfOrigin”: “DE”,
“CompetitorID”: “”,
“ProductGroup”: “001”,
“BaseUnit”: “PC”,
“ItemCategoryGroup”: “NORM”,
“ProductHierarchy”: “001002003”,
“Division”: “10”,
“VarblPurOrdUnitIsActive”: “1”,
“VolumeUnit”: “M3”,
“MaterialVolume”: 1.250,
“ANPCode”: “0”,
“Brand”: “BR01”,
“ProcurementRule”: “”,
“ValidityStartDate”: “2026-01-01”,
“LowLevelCode”: “000”,
“ProdNoInGenProdInPrepackProd”: “”,
“SerialIdentifierAssgmtProfile”: “”,
“SizeOrDimensionText”: “10x20x30”,
“IndustryStandardName”: “ISO-9001”,
“ProductStandardID”: “4004123456789”,
“InternationalArticleNumberCat”: “E8”,
“_Plant”: [
{
“Product”: “MAT-ABCD”,
“Plant”: “1234”,
“PurchasingGroup”: “001”,
“CountryOfOrigin”: “DE”,
“RegionOfOrigin”: “01”,
“ProductionInvtryManagedLoc”: “1001”,
“ProfileCode”: “”,
“ProfileValidityStartDate”: “2026-07-31”,
“_StorageLocation”: [
{
“Product”: “MAT-ABCD”,
“Plant”: “1234”,
“StorageLocation”: “1001”,
“WarehouseStorageBin”: “BIN-A1”,
“MaintenanceStatus”: “KEDV”
}
]
}
],
“_SalesDelivery”: [
{
}
],
“_ProductSalesTax”: [
{
}
]
},
“_log”: [
{
}
]
}
Response:
Henceforth from Response, it’s evident that ID field is having BP-BASE as per input and response log has Message code and Message which clearly indicates that BASE layer is executed successfully.
TESTING BS-1/PROJECTION-1:
- Since Projection-1 holds limited fields from Base-1, Our JSON should have all values mapped to the fields that we exposed in Projection-1Abstract entities. It’s not mandatory to pass all association fields of Item, but if single field from Header or Header direct association is/are missed then execution wont happen and it leads to OData error.
- All the entities point towards same action logic, we will identify the response based on the response structure designed in each Abstract entity.
- INPUT URL:
- /sap/opu/odata4/sap/zsb_mm_mat_generic_payload/srvd/sap/zmat_api_serv_def/0001/ZP_MATERIAL_MASTER_BS1(‘ ‘)/
- {{ Schema namespace in metadata->starts with com.sap.gateway…}}
- .l_material_api
So input URL will be 1 + 2 + 3.
- INPUT JSON:
{
“Product”: “ABC”,
“Id”: “BS-1”,
“AckCode”: “ACK2026”,
“Status”: “A”,
“_MaterialData”: {
“Product”: “ABC”,
“_Plant”: [
{
“Product”: “ABC”,
“Plant”: “1111”
} ],
“_SalesDelivery”: [
{
“Product”: “ABC”,
“ProductSalesOrg”: “1234”,
“ProductDistributionChnl”: “09”,
“Group1”: “ZZZ”
}
]
},
“_log”: [
{
}
]
}
- In this input JSON note the _SalesDelivery, where the name Group1 is defined as per Business Scenario whereas the actual naming of the standard field is
- so while executing we need this mapping to happen perfectly from Group1 to in Base Behaviour class.
Debug Mode while execution-INPUT Deep structure:
Two things to note from above Debug Mode:
- Even though user pass input in Group1 at gatewaylevel, but still the value gets mapped correctly to FirstSalesSpecProductGroup due to Abstract level mapping in Input Keys parameter.
- Under the tree _SalesDeliery and other associations as well – All fields from base are still showing but from our redefinition we can pass only the required fields.
Debug Mode while execution-RESPONSE Deep structure:
One thing to notice from above Result deep structure:
- Even though we map data to Messagecode and Message in _Log Association, it still maps to ErrorCode and ErrMsg at Projection 1 level response.
RESPONSE:
- From above screenshot, it’s evident that BS-1 is executed sucessfully and response log gets mapped to renamed fields Errorcode and ErrorMsg.
TESTING BS-2/PROJECTION-2:
- Since Projection-2 holds limited fields from Base-1, Our JSON should have all values mapped to the fields that we exposed in Projection-2 Abstract entities. The execution will happen as similar to Base and Projection/BS1.
- INPUT URL:
- /sap/opu/odata4/sap/zsb_mm_mat_generic_payload/srvd/sap/zmat_api_serv_def/0001/ZP_MATERIAL_MASTER_BS2(‘ ‘)/
- {{ Schema namespace in metadata->starts with com.sap.gateway…}}
- .l_material_api
So input URL will be 1 + 2 + 3.
- INPUT JSON:
{
“Product”: “MAT-ABCD”,
“Id”: “BS-2”,
“_log”: [
{
}
]
}
Note: Here as well similar mapping happens to Errorcode and ErrorMsg fields, so im not pasting debug screenshots again. Please refer to Projection/BS1 layer for the same.
Response:
- From above screenshot, it’s evident that BS-2 is executed sucessfully and response log gets mapped to renamed fields Errorcode and ErrorMsg.
Limitations:
- Only RAP Normal Deep Actions supported here and static and instance RAP Actions are strictly not supported since it cant be redefined.
- This redefinition will work only for Deep Actions with OData V4. For Normal Action without Deep syntax, it will not work. (Note: Regarding RAP Deep Actions and OData V4, I have already explained in my earlier blog – RAP – Deep Static Action with OData V4 ).
- Keyword SUB is not supported in RAP Actions without the term corresponding in it. If we use it directly then for some of the SAP patch level, using a=sub combined with hierarchy mapping caused a hard SYSTEM_CORE_DUMPED (Signal 11) in the ABAP Kernel during ADT activation as below.
- To avoid the kernel crash, I avoided the usage of syntax SUB Directly and relied on SUB with CORRESPONDING as below.
deep mapping for ya_material_api_parameter corresponding {
Id = Id;
Product = Product;
AckCode = AckCode;
Status = Status;
sub _MaterialData = _MaterialData;
sub _log = _log;
}
- Instead of using Behaviour Projection in higher layers, If we use Behaviour Interface it doesnt intent to support even in RAP V4. It gives error in Service as below. (i..e to enable Behaviour Interface, we must add provider contract transactional_interface at Projection layer ).
- Cardinality is not supported in Deep Result at Projection/Interface levels. The cardinality given in base layer is directly applicable to all the redefinitions.
- Since Abstract Entities are action parameter and they wont directly refer to standard structures/cds views, henceforth manual field mapping should be done.
CONCLUSION:
Henceforth, in this way RAP Action Parameter Redefinition is used to decouple Material Master API at base and projection layer. Not only Material Master API for interface, but also this approach can use used for Fiori app integrations as well based on the requirement. Moreover, I can say this is a very good feature given by RAP to use Mandatory Action Parameter based on business requirement. This makes the backend highly reusable and reliable. Also, OData V4 on top of this makes the metadata lighter and powerful.
“}]]
Read More Technology Blog Posts by Members articles
#abap