Business Background

You would like to create a Purchase Order based on the Purchase Requisition, but once the requested quantity is exceeded, you won't like to allow further creations of Purchase Orders related to this Purchase Requisition? Then you are right here in this How-To-Blog.

The SAP standard behavior is that the Purchase Requisition is a reference document and that the requested quantity is not a hard limit. With that you are quickly able to create another Purchase Order, e.g. on short notice, using the same Purchase Requisition as template. But some wouldn't like to have this, so you can implement the final check BAdI for Purchase Order.

Role / Prerequisites

For implementing a BAdI you need ABAP Know-How and the authorization (usually the Admin, role SAP_BR_ADMINISTRATOR) to create it.

Steps:

  1. Logon to your Fiori Launchpad
  2. Open the app "Custom Field and Logic" (#CustomField-develop)
  3. Move to tab "Custom Logic"
  4. Press "Create" button in upper right corner to create a new BAdI implementation
  5. A popup opens,
    1. Select the Business Context: "Procurement: Purchasing Document"
    2. Select the BAdI: "Check of Purchase Order Before Saving"
    3. Add a meaningful description
    4. Change Implementation ID, if wished
    5. Press "Create"

PO_FINAL_CHECK_Create_Implementation_Non_existing.PNG

Remark - Error in create scenario:

In case the Implementation ID is greyed out (like below) and you get an error popup once you pressed on "Create" having the error message: "Implementation YY1_PO_BEFORE_SAVE already exists. Use the editor view to create a draft." Press "Ok" and "Cancel" in the creation dialog and search for the implementation by using the search field above the table and "YY1_PO_BEFORE_SAVE" as search term. Once the search result is shown press on the line to open the existing implementation. This implementation then needs to be enhanced.

PO_FINAL_CHECK_Create_Implementation.PNG

Once you pressed create (or opened your existing implementation), you will find the screen below, where you can implement your coding, having a sample implementation from SAP.

PO_FINAL_CHECK_Sample_Implementation.PNG

Within available fields you can see the internal tables you can use for further processing. The both with _db representing the data from database, in case the Purchase Order was already saved and is in edit mode right now.

Once the implementation is finished you can press "Publish" to make it active.

Implementation of check logic

Goal

Check logic to throw an error message in case the open quantity of a Purchase Requisition Item is exceeded during Purchase Order creation

5 Steps for the Example Implementation

Step 1: Get the quantity of the Purchase Order Items per Purchase Requisition Item

Step 2: Loop through used Purchase Requisition Items and get the open quantity

Step 3: Handle change mode of a Purchase Order - calculate quantity correctly

Step 4: How-To throw a error message

Step 5: Round-Off

 

Step 1: Get the quantity of the Purchase Order Items per Purchase Requisition Item

Step 1

*   sum up all entered quantity in the purchase order, grouped by the purchase requisistion items to have the total value per purchase requisistion item and exclude items without a purchase requistion


    SELECT purchaserequisition, purchaserequisitionitem, SUM( OrderQuantity ) AS pr_total_qty FROM @purchaseorderitems AS po_items

      WHERE purchaserequisition IS NOT INITIAL
      GROUP BY purchaserequisition, purchaserequisitionitem
      INTO TABLE @DATA(lt_pr_grouped_qty).

We are selecting from the Purchase Order Items (@purchaseorderitems), which quantity was entered over all purchase order items, summed up by purchase requisition item. We store all these data in an internal table called lt_pr_grouped_qty having three columns (Purchase Requisition - purchaserequisition, Purchase Requisition Item - purchaserequistionitem, Total Quantity Used in current Purchase Order - pr_total_qty).

Step 2: Loop through used Purchase Requisition Items and get the open quantity

Step 1

[...coding from above...]

Step 2.1

*   loop through all grouped items
    LOOP AT lt_pr_grouped_qty INTO DATA(ls_pr_grouped_qty).

Step 2.2

*     SELECT from the PurchaseRequisition view the requested as well as the already ordered quantity based on the purchase requisistion used for the Purchase Order cration
      SELECT SINGLE RequestedQuantity, OrderedQuantity FROM I_PurchaseRequisition_Api01 INTO @DATA(ls_purchaserequisitionitem)
        WHERE PurchaseRequisition = @ls_pr_grouped_qty-purchaserequisition
        AND PurchaseRequisitionitem = @ls_pr_grouped_qty-purchaserequisitionitem.

Step 2.3

*     Calculate the quantity which would remain
      DATA(lv_qty) = ls_purchaserequisitionitem-RequestedQuantity - ls_purchaserequisitionitem-OrderedQuantity - ls_pr_grouped_qty-pr_total_qty.

Step 2.1

    ENDLOOP.

Afterwards we are looping through the just created internal table lt_pr_grouped_qty (Step 2.1). Within that we are selecting from the database the total requested quantity and the already ordered quantity per Purchase Requisition Item using the item we have currently at our loop-position (Step 2.2). Then we do simply mathematics, from the requested quantity subtracting the ordered quantity and the entered quantity in the Purchase Order (total quantity of all items referring to the same Purchase Requisition Item) (Step 2.3).

Step 3: Handle change mode of a Purchase Order - calculate quantity correctly

Step 2.3

[...coding from above...]

Step 3.1

*     If we already have a ordered Purchase Order we need to consider the ordered quantity also in the calculation
      IF purchaseorder-purchaseorder IS NOT INITIAL.

Step 3.2

*       Select the quantity per purchase requisistion item from stored purchase order items on database
        SELECT purchaserequisition, purchaserequisitionitem, SUM( OrderQuantity ) AS pr_total_qty FROM @purchaseorderitems_db AS po_items_db
            WHERE purchaserequisition = @ls_pr_grouped_qty-purchaserequisition
            AND purchaserequisitionitem = @ls_pr_grouped_qty-purchaserequisitionitem
            GROUP BY purchaserequisition, purchaserequisitionitem
            INTO TABLE @DATA(lt_pr_grouped_qty_db).

Step 3.3

*       Loop through the already ordered quantity and add it on top
        LOOP AT lt_pr_grouped_qty_db INTO DATA(ls_pr_grouped_qty_db).
          lv_qty = lv_qty + ls_pr_grouped_qty_db-pr_total_qty.
        ENDLOOP.

Step 3.1

      ENDIF.

Step 2.1

    ENDLOOP.

After the calculation of Step 2.3 and the end of our loop, we need to handle the change mode. The Purchase Order was ordered, but afterwards something needs to be adopted related to the quantity. So the already ordered quantity is fine and should not effect here, just the difference should be relevant. First (Step 3.1) we need to check whether a purchaser oder is already existing, so we are checking for a Purchase Order Number. Then we are selecting similar as in Step 1, but at this time from the stored Purchase Order Items (@purchaseorderitems_db) and based on the current Purchase Requisition Item we have in our loop (Step 3.2). The same Purchase Requisition Item could be used for multiple Purchase Order Items, so we are looping through all and adding the already ordered quantity again in our calculation as those are not relevant anymore and the Purchase Requisition doesn't consider them as open (Step 3.3).

Step 4: How-To throw a error message

Step 3.3

[...coding from above...]

Step 4.1

*     Throw error in case the quantity is exceeded
      IF lv_qty < 0.

Step 4.2

*       Format the Quantity to a positiv number
        DATA(lv_qty_pos) = lv_qty * -1.

Step 4.3

*       Here a non-translatable message is raised just for demo purpose
        ls_message-messagetype = 'E'.
        ls_message-messagevariable1 = 'Exceeded quantity for PR '
                                      && ls_pr_grouped_qty-purchaserequisition && '/' && ls_pr_grouped_qty-purchaserequisitionitem && ' by: ' && lv_qty_pos.

Step 4.4

        APPEND ls_message TO messages.

Step 3.1

      ENDIF.

Step 2.1

    ENDLOOP.

The quantity stored in variable lv_qty is below zero, in case you are trying to order more then available, based on the quantity in the purchase requisition (see calculation above). In case it is below zero, we would like to throw an error and therewith prevent the saving of the Purchase Order (Step 4.1). For having a nice error text, we are converting the quantity to positive value (Step 4.2). For demo purpose we are raising an hardcoded error message. The 'E' in ls_message-messagetype indicates the criticality as error and ls_message-messagevariable1 is used for our own build text having the Purchase Requisition Item and the exceeded quantity included (Step 4.3) An example on how to use a message code is shown below. In Step 4.4 we append our ls_message to the returning parameter messages where all message are stored, in case we have multiple.

Step 5: Round-Off

*      If a translatable text for the message is needed please use code lists. For on how this can be done in the coding uncomment the lines commented
*      by an " and replace the CDS yy1_mm_po_ext fields by yours created in your code list. In the code list the message text and its translation is stored.
*      The text is then read implicitily by the used language if the translation is available in the code list
        
       SELECT SINGLE FROM yy1_mm_po_ext fields description WHERE code = '01' INTO @data(lv_message).
       IF sy-subrc = 0.
*         If the quantity is exceeded an error message is raised from a message class
*         The error prevents a saving of the purchase order, i.e. the purchasing group can not be changed anymore
         ls_message-messagetype = 'E'.
          ls_message-messagevariable1 = lv_message.
          APPEND ls_message TO messages.
       ENDIF.

As mentioned in Step 4, if you would like to have translatable error message, you can orient yourself on below code snippet, which is also part of SAP provided sample implementation of this BAdI.

So a custom CDS view can be created where the description is read out per code and can be translated.


Complete Implementation of this example

*   The example implementation shows how it is prevented that the purchase order can be created once the purchase requisistion
*   quantity has exceeded

   
*   Structure for messages
    DATA: ls_message LIKE LINE OF messages.

*   sum up all entered quantity in the purchase order, grouped by the purchase requisistion items to have the total value per purchase requisistion item and exclude items without a purchase requistion
    SELECT purchaserequisition, purchaserequisitionitem, SUM( OrderQuantity ) AS pr_total_qty FROM @purchaseorderitems AS po_items
      WHERE purchaserequisition IS NOT INITIAL
      GROUP BY purchaserequisition, purchaserequisitionitem
      INTO TABLE @DATA(lt_pr_grouped_qty).

*   loop through all grouped items
    LOOP AT lt_pr_grouped_qty INTO DATA(ls_pr_grouped_qty).

*     SELECT from the PurchaseRequisition view the requested as well as the already ordered quantity based on the purchase requisistion used for the Purchase Order cration
      SELECT SINGLE RequestedQuantity, OrderedQuantity FROM I_PurchaseRequisition_Api01 INTO @DATA(ls_purchaserequisitionitem)
        WHERE PurchaseRequisition = @ls_pr_grouped_qty-purchaserequisition
        AND PurchaseRequisitionitem = @ls_pr_grouped_qty-purchaserequisitionitem.

*     Calculate the quantity which would remain
      DATA(lv_qty) = ls_purchaserequisitionitem-RequestedQuantity - ls_purchaserequisitionitem-OrderedQuantity - ls_pr_grouped_qty-pr_total_qty.

*     If we already have a ordered Purchase Order we need to consider the ordered quantity also in the calculation
      IF purchaseorder-purchaseorder IS NOT INITIAL.
       
*       Select the quantity per purchase requisistion item from stored purchase order items on database
        SELECT purchaserequisition, purchaserequisitionitem, SUM( OrderQuantity ) AS pr_total_qty FROM @purchaseorderitems_db AS po_items_db
            WHERE purchaserequisition = @ls_pr_grouped_qty-purchaserequisition
            AND purchaserequisitionitem = @ls_pr_grouped_qty-purchaserequisitionitem
            GROUP BY purchaserequisition, purchaserequisitionitem
            INTO TABLE @DATA(lt_pr_grouped_qty_db).

*       Loop through the already ordered quantity and add it on top
        LOOP AT lt_pr_grouped_qty_db INTO DATA(ls_pr_grouped_qty_db).
          lv_qty = lv_qty + ls_pr_grouped_qty_db-pr_total_qty.
        ENDLOOP.
      ENDIF.

*     Throw error in case the quantity is exceeded
      IF lv_qty < 0.
       
*       Format the Quantity to a positiv number
        DATA(lv_qty_pos) = lv_qty * -1.

*       Here a non-translatable message is raised just for demo purpose
        ls_message-messagetype = 'E'.
        ls_message-messagevariable1 = 'Exceeded quantity for PR '
                                      && ls_pr_grouped_qty-purchaserequisition && '/' && ls_pr_grouped_qty-purchaserequisitionitem && ' by: ' && lv_qty_pos.
        APPEND ls_message TO messages.
       
**      If a translatable text for the message is needed please use code lists. For on how this can be done in the coding uncomment the lines commented
**      by an " and replace the CDS yy1_mm_po_ext fields by yours created in your code list. In the code list the message text and its translation is stored.
**      The text is then read implicitily by the used language if the translation is available in the code list
*       
*       SELECT SINGLE FROM yy1_mm_po_ext fields description WHERE code = '01' INTO @data(lv_message).
*       IF sy-subrc = 0.
**         If the quantity is exceeded an error message is raised from a message class
**         The error prevents a saving of the purchase order, i.e. the purchasing group can not be changed anymore
*          ls_message-messagetype = 'E'.
*          ls_message-messagevariable1 = lv_message.
*          APPEND ls_message TO messages.
*       ENDIF.

      ENDIF.

    ENDLOOP.

Disclaimer: The usage of the coding example is on your own risk. SAP is providing this as an example only, there is no claim for completeness and infallibility. SAP will not support in case of any issues related to this implementation.