Enterprise Resource Planning Blogs by SAP
Get insights and updates about cloud ERP and RISE with SAP, SAP S/4HANA and SAP S/4HANA Cloud, and more enterprise management capabilities with SAP blog posts.
cancel
Showing results for 
Search instead for 
Did you mean: 
Vincent_Zhu
Product and Topic Expert
Product and Topic Expert

This is a detailed step-by-step technical guide document to introduce a Developer Extensibility case followed by this blog.

 

1. Case Background:

We frequently hear inquiries from clients who have enabled batch management, asking whether there are reminders for nearing expiry materials and batch query reports for such materials. In standard products, retrieving relevant information often requires searching through multiple reports, such as inventory reports and batch details. To meet this demand, we have developed a Material Shelf Life Management App on S/4HANA public cloud through developer extensibility (also known as cloud ABAP development), aiming to help enterprises intelligently manage material Shelf Life and enhance supply chain management efficiency. Regarding the business value of this use case, you can refer to this blog for details.

shelf1.png

 

Due to varying business management needs, each company may define different ranges for nearing expiry reminders. The Customizable Warning Threshold Configuration App allows users to set customized ranges for when to display red, yellow, or green warning lights, meeting the diverse customization needs of different enterprises for expiry management.

shelf2.png

 

2. Backend Service Development:

2.1 Create CDS Data Model

Create Data Definition: YCDS_DUE_MAT_LIST

 

@AbapCatalog.sqlViewName: 'YDUEMATMANGE'
@AbapCatalog.compiler.compareFilter: true
//@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Material Due Date List'
define view YCDS_DUE_MAT_LIST as select from I_Batch
inner join YCDS_SUM_STOCK_01 on YCDS_SUM_STOCK_01.Material = I_Batch.Material
                                       and YCDS_SUM_STOCK_01.Plant = I_Batch.Plant
                                       and YCDS_SUM_STOCK_01.Batch = I_Batch.Batch
association [1..1] to I_Product on I_Product.Product = I_Batch.Material
association [1..1] to I_ProductDescription on I_ProductDescription.Product = I_Batch.Material
                                          and I_ProductDescription.Language = $session.system_language
association [1..1] to I_Plant on I_Plant.Plant = I_Batch.Plant
                              and I_Plant.Language = $session.system_language
association [1..1] to I_Supplier on I_Supplier.Supplier = I_Batch.Supplier                           
{
  key I_Batch.Material,
  key I_Batch.Batch,
  key I_Batch.Plant,
  key I_Batch.Supplier,
  key I_Batch.BatchBySupplier,
//  key YCDS_SUM_STOCK_01.InventoryStockType,
//  key YCDS_SUM_STOCK_01.MatlDocLatestPostgDate,
  YCDS_SUM_STOCK_01.StorageLocation,
  YCDS_SUM_STOCK_01.MatlWrhsStkQtyInMatlBaseUnit,
  I_Batch.MatlBatchIsInRstrcdUseStock,
  I_Batch.ShelfLifeExpirationDate,
  I_Batch.ManufactureDate,
  I_Product.BaseUnit,
  I_ProductDescription.ProductDescription,
  I_Plant.PlantName,
  I_Supplier.SupplierName,
  case when I_Batch.MatlBatchIsInRstrcdUseStock = 'X' then 'Restricted' else 'Unrestricted' end as BatchStatus,
  $session.system_date as curr_date
}
where I_Batch.ShelfLifeExpirationDate is not initial
  and YCDS_SUM_STOCK_01.MatlWrhsStkQtyInMatlBaseUnit <> 0

 

 

Create Data Definition: YCDS_DUE_MAT_LIST2

 

@AbapCatalog.sqlViewName: 'YDUEMATMANGE2'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Material Due Date List'
define view YCDS_DUE_MAT_LIST2 as select from YCDS_DUE_MAT_LIST
association [1..1] to I_StorageLocation on I_StorageLocation.StorageLocation = YCDS_DUE_MAT_LIST.StorageLocation
                                       and I_StorageLocation.Plant           = YCDS_DUE_MAT_LIST.Plant
{
  key YCDS_DUE_MAT_LIST.Material,
  key YCDS_DUE_MAT_LIST.Plant,
  key YCDS_DUE_MAT_LIST.StorageLocation,
  key YCDS_DUE_MAT_LIST.Batch,
  key YCDS_DUE_MAT_LIST.Supplier,
//  key YCDS_DUE_MAT_LIST.InventoryStockType,
//  key YCDS_DUE_MAT_LIST.MatlDocLatestPostgDate,
  YCDS_DUE_MAT_LIST.ProductDescription,
  YCDS_DUE_MAT_LIST.PlantName,
  I_StorageLocation.StorageLocationName,
  YCDS_DUE_MAT_LIST.BatchBySupplier,
  YCDS_DUE_MAT_LIST.SupplierName,
  MatlWrhsStkQtyInMatlBaseUnit,
  YCDS_DUE_MAT_LIST.BaseUnit,
  YCDS_DUE_MAT_LIST.ShelfLifeExpirationDate,
  YCDS_DUE_MAT_LIST.ManufactureDate,
  YCDS_DUE_MAT_LIST.BatchStatus,
  dats_days_between(YCDS_DUE_MAT_LIST.curr_date,YCDS_DUE_MAT_LIST.ShelfLifeExpirationDate) as remaining_days
} 

 

 

Create Data Definition: YCDS_SUM_STOCK_01

 

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sum of Stock 01'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
  serviceQuality: #X,
  sizeCategory: #S,
  dataClass: #MIXED
}
define view entity YCDS_SUM_STOCK_01 as select from I_MaterialStock_2
{
  key Material,
  key Plant,
  key StorageLocation,
  key Batch,
  key MaterialBaseUnit,
  @Semantics.quantity.unitOfMeasure: 'MaterialBaseUnit'
  @Aggregation.default: #SUM
  sum( MatlWrhsStkQtyInMatlBaseUnit ) as MatlWrhsStkQtyInMatlBaseUnit
}
where InventoryStockType = '01'
group by Material, Plant, StorageLocation, Batch, MaterialBaseUnit

 

 

2.2 Create Metadata Extensions

Create new Metadata Extension: YC_DUE_MAT_LIST

 

@Metadata.layer: #CUSTOMER

@UI.headerInfo.title.type: #STANDARD
@UI.headerInfo.title.label: 'Material Shelf Life Management'
@UI.headerInfo.description.type: #STANDARD
@UI.headerInfo.typeName: 'Material Due List'
@UI.headerInfo.typeNamePlural: 'Material Shelf Life Management'

annotate view YC_DUE_MAT_LIST with
{

  @UI.facet: [{ id: 'Material',
                purpose: #STANDARD,
                type: #IDENTIFICATION_REFERENCE,
                label: 'Material',
                position: 10}
                 ]

  @UI:{ lineItem: [{ position: 10 }],
        lineItem: [{label: 'Material'}],
        identification: [{ position: 10 }],
        selectionField: [{ position: 20 }]
        }
  Material;
  @UI:{ lineItem: [{ position: 20 }],
        lineItem: [{label: 'Material Description'} ],
        identification: [{ position: 20 }]
        }
  @UI.lineItem: [{exclude: true}]
  ProductDescription;
  @UI:{ lineItem: [{ position: 30 }],
        lineItem: [{label: 'Plant'}],
        identification: [{ position: 30 }],
        selectionField: [{ position: 10 }]
        }
  Plant;
  @UI:{ lineItem: [{ position: 40 }],
        lineItem: [{label: 'Plant Name'}],
        identification: [{ position: 40 }]
        }
  @UI.lineItem: [{exclude: true}]
  PlantName;
  @UI.lineItem: [{ position: 50 }]
  @UI.lineItem: [{label: 'Storage Location'}]
  @UI.identification: [{ position: 50 }]
  @UI.selectionField: [{position: 50}]
  StorageLocation;
  @UI.lineItem: [{ position: 60 }]
  @UI.lineItem: [{label: 'Storage Location Name'}]
  @UI.identification: [{ position: 60 }]
  @UI.lineItem: [{exclude: true}]
  StorageLocationName;
  @UI.lineItem: [{ position: 70 }]
  @UI.lineItem: [{label: 'Batch'}]
  @UI.identification: [{ position: 70 }]
  @UI.selectionField: [{position: 70}]
  Batch;
  @UI.lineItem: [{ position: 80 }]
  @UI.lineItem: [{label: 'Supplier Batch'}]
  @UI.identification: [{ position: 80 }]
  @UI.selectionField: [{position: 80}]
  BatchBySupplier;
  @UI.lineItem: [{ position: 90 }]
  @UI.lineItem: [{label: 'Supplier'}]
  @UI.identification: [{ position: 90 }]
  Supplier;
  @UI.lineItem: [{ position: 100 }]
  @UI.lineItem: [{label: 'Supplier Name'}]
  @UI.identification: [{ position: 100 }]
  @UI.lineItem: [{exclude: true}]
  SupplierName;
  @UI.lineItem: [{ position: 110 }]
  @UI.lineItem: [{label: 'Unrestricted Inventory Qty'}]
  @UI.identification: [{ position: 110 }]
  MatlWrhsStkQtyInMatlBaseUnit;
  @UI.lineItem: [{ position: 120 }]
  @UI.lineItem: [{label: 'Unit'}]
  @UI.identification: [{ position: 120 }]
//  @UI.lineItem: [{exclude: true}]
  @UI.hidden: true
  BaseUnit;
  @UI.lineItem: [{ position: 130 }]
  @UI.lineItem: [{label: 'Expiration Date'}]
  @UI.identification: [{ position: 130 }]
  ShelfLifeExpirationDate;
  @UI.lineItem: [{ position: 140 }]
  @UI.lineItem: [{label: 'Manufacture Date'}]
  @UI.identification: [{ position: 140 }]
  @UI.lineItem: [{exclude: true}]
  ManufactureDate;
  @UI.lineItem: [{ position: 150 }]
  @UI.lineItem: [{label: 'Batch Status'}]
  @UI.identification: [{ position: 150 }]
  @UI.lineItem: [{exclude: true}]
  BatchStatus;
  @UI.lineItem: [{ position: 160, importance: #HIGH }]
  @UI.lineItem: [{label: 'Remaining Days'}]
  @UI.identification: [{ position: 160 }]
  remaining_days;
  @UI.lineItem: [{ position: 170, value: 'due_range', criticality: 'Criticality' }]
  @UI.lineItem: [{label: 'Due Date Range'}]
  @UI.identification: [{ position: 170 }]
  due_range;    
//  @UI.hidden: true
//  InventoryStockType;
//  @UI.hidden: true
//  MatlDocLatestPostgDate;
  @UI.hidden: true
  Criticality;
}

 

 

2.3 Create Service Definition and Service Binding

shelf3.png

shelf4.png

 And then we can preview the published service:

shelf5.png

 

3. Frontend App Development

First create destination on BTP:

shelf6.png

 

Then open BAS service, and choose SAP Fiori Worklist Application:

shelf7.png

 

After publish, and authorization setup, we can see the App like below:

shelf8.png