1. Introduction
While creating sales orders from SAP or in WUI it is often possible that similar sales orders are placed more than once. In that case it would be nice to have a preventive measure by checking in the system if there are any existing sales orders that are similar to the one that is being created. Finding one probable duplicate order and being warned about the same will ensure that before we save a new order.
1. MAJOR FEATURES
When a sales order is entered in CRM, before saving it, it may be necessary to perform a duplicate order check in BADI ORDER_SAVE, depending on the conditions described below.
The duplicate order check should be activated for the combination of transaction type, sales area and media type. (For example: for standard orders within France the check is activated, for standard orders within the UK the check is deactivated).
Sales Area – The sales area provides a mechanism to turn the functionality on or off per country.
Transaction Type – Should be checked to make the difference between sales orders. (For example: excluding debit or credit memo’s.
Media Type – E.g. Fax, E-mail, telephone. (For example: web orders and EDI will be excluded
Contact Person – The check on contact person should be turned on or off per activation area above. Example: When the check on contact person is switched ON the check will only consider orders created for the same ship-to and contact person. When the check is turned OFF there is a check between all orders for a certain ship-to.
Number of days – A number of days should be included to know how many days back in time should be checked. This can differ per activation area. EG for France 8 days, for UK 4 days.
Number of items – In the as-is situation 4 items are checked. If 4 items are identical in 2 orders, the duplicate check sees the order as a possible duplicate order. In the to-be situation it must be possible that the number of items is variable per activation area above.
1. TECHNICAL DETAILS
For the purpose of the duplicate order check, key figures like sales organization, distribution channel, division, transaction type and media type is looked into for the order that is to be saved.
This is done using a table where certain flags and ranges are stored. For example, if the flag duplicate_order_check is enabled this means duplicacy needs to be checked. Similarly, if contact_per_chk is enabled then the contact person has to be compared as well. The days_chk determines maximum how many days back we should check for duplicates and for items_chk determines how many items need to be similar in order to qualify as a duplicate.
Once a duplicate order is found, the customer would like to be warned that what they are trying to create might already exist in the system. This can be done simply by using a popup message box. This part we can come it later but the main requirement is to find the details of the current sales order that is about to be saved.
CRM ABAP
In the SAP system it is possible using BADI ORDER_SAVE->CHECK_BEFORE_SAVE. So we need to implement this badi first.
In the method CHECK_BEFORE_SAVE the importing parameter in concern is IV_GUID. So all the details of the order need to be fetched through the GUID.
First and foremost, this is only applicable in foreground and to prevent this we check.
IF sy-batch IS NOT INITIAL.
RETURN.
ENDIF.
Some of the data can be retrieved from the SAP memory.
* Fetch partner data to find
* determine layout
CALL FUNCTION 'COM_PARTNER_LAYOUT_GET_UI'
EXPORTING
iv_subscreen_id = lco_scr1000
IMPORTING
es_screen_layout = lwa_screen_layout
EXCEPTIONS
no_layout_defined = 1
OTHERS = 2.
IF sy-subrc NE 0.
* screen data could not be fetched so return
RETURN.
ENDIF.
* when its a new order object id is initial
IF lwa_screen_layout-object_id IS NOT INITIAL.
RETURN.
ENDIF.
This data helps us to find the details of the partners like sold to party, ship to party, contact etc.
* get partners
CALL FUNCTION 'COM_PARTNER_TO_DISPLAY_OW'
EXPORTING
iv_partnerset_guid = lwa_screen_layout-partnerset
iv_determ_proc = lwa_screen_layout-determ_proc
IMPORTING
et_partner = lt_partner_to_disp
EXCEPTIONS
partnerset_not_found = 1
OTHERS = 2.
IF sy-subrc NE 0.
* screen data could not be fetched so return
RETURN.
ENDIF.
lt_partner_to_display[] = lt_partner_to_disp[].
* get ship to party
READ TABLE lt_partner_to_display
INTO lwa_partner_to_display
WITH KEY partner_fct = lco_partner_2.
IF sy-subrc EQ 0.
lv_ship_to_c = lwa_partner_to_display-partner_number.
ENDIF.
* get contact person
READ TABLE lt_partner_to_display
INTO lwa_partner_to_display
WITH KEY partner_fct = lco_partner_15.
IF sy-subrc EQ 0.
lv_contact_c = lwa_partner_to_display-partner_number.
ENDIF.
But we will need the GUID to fetch additional information
* Fetch sales org/dis channel/division from orgman
INSERT iv_guid
INTO TABLE lt_header_guid.
CALL FUNCTION 'CRM_INTLAY_GET_DATA'
EXPORTING
iv_guid = iv_guid
iv_kind = lco_kind_a
iv_interfacename = lco_int_orgman
IMPORTING
es_interfacevalue = lwa_orgman_ui
EXCEPTIONS
error_occurred = 1
no_valid_guid = 2.
IF sy-subrc EQ 0.
lv_sales_org_c = lwa_orgman_ui-sales_org_short.
lv_dis_channel_c = lwa_orgman_ui-dis_channel.
lv_division_c = lwa_orgman_ui-division.
ELSE.
* failed to fetch organization data so return
RETURN.
ENDIF.
* Fetch category(media type) from sales UI
CALL FUNCTION 'CRM_INTLAY_GET_DATA'
EXPORTING
iv_guid = iv_guid
iv_kind = lco_kind_a
iv_interfacename = lco_int_sales
IMPORTING
es_interfacevalue = lwa_sales_ui
EXCEPTIONS
error_occurred = 1
no_valid_guid = 2.
IF sy-subrc EQ 0.
lv_media_type_c = lwa_sales_ui-category.
ELSE.
* failed to fetch media type data so return
RETURN.
ENDIF.
* Transaction type
lv_transaction_type_c = lwa_screen_layout-process_type.
This gives us almost all information we require for checking the duplicate order, except the item level check. This is done using the standard function
CALL FUNCTION 'CRM_ORDER_READ'
EXPORTING
it_header_guid = lt_header_guid
IMPORTING
et_orderadm_h = lt_orderadm_h
et_orderadm_i = lt_orderadm
et_product_i = lt_product_i
EXCEPTIONS
document_not_found = 1
error_occurred = 2
document_locked = 3
no_change_authority = 4
no_display_authority = 5
no_change_allowed = 6
OTHERS = 7.
IF sy-subrc <> 0.
* unable to fetch posting date so return
RETURN.
ELSE.
READ TABLE lt_orderadm_h
INTO lwa_orderadm_h
INDEX 1.
ENDIF.
In the next step we can check on the duplicate order table to check if the checking is enabled for the current order. In case duplicate check is disabled, we need not proceed further.
* Fetch data from switch table
SELECT SINGLE *
FROM yorder_check
INTO lwa_duplicate
WHERE sales_org = lv_sales_org_c
AND dis_channel = lv_dis_channel_c
AND division = lv_division_c
AND transaction_type = lv_transaction_type_c
AND media_type = lv_media_type_c.
* check if record is there and duplicate check is active else return
IF sy-subrc NE 0
OR lwa_duplicate-duplicate_ord_chk IS INITIAL.
RETURN.
ENDIF.
Once we know that we have to check for duplicates, we start looking for existing records in between today's date and the date before with days check.
* determine posting date range
lwa_post_dt-sign = lco_range_i.
lwa_post_dt-option = lco_range_bt.
lwa_post_dt-low = lwa_orderadm_h-posting_date - lwa_duplicate-days_chk.
lwa_post_dt-high = sy-datum.
APPEND lwa_post_dt TO lt_post_dt.
* select GUIDs from orderadm_h table
SELECT *
FROM crmd_orderadm_h
INTO TABLE lt_header
WHERE posting_date IN lt_post_dt
AND process_type EQ lv_transaction_type_c.
IF lt_header[] IS INITIAL.
RETURN.
ENDIF.
Now that we have lt_header[] with all the sales orders which fall within the same date range we can fetch all information related to these orders.
* header has values this means records exist within the date range
LOOP AT lt_header INTO lwa_header.
APPEND lwa_header-guid TO lt_guid_sorted.
ENDLOOP.
SORT lt_guid_sorted.
DELETE ADJACENT DUPLICATES FROM lt_guid_sorted.
lt_header_guid[] = lt_guid_sorted[].
* read the record details
CALL FUNCTION 'CRM_ORDER_READ'
EXPORTING
it_header_guid = lt_header_guid
IMPORTING
et_activity_h = lt_activity2
et_orderadm_i = lt_orderadm2
et_product_i = lt_product_i2
et_orgman = lt_orgman2_s
et_partner = lt_partner2
EXCEPTIONS
document_not_found = 1
error_occurred = 2
document_locked = 3
no_change_authority = 4
no_display_authority = 5
no_change_allowed = 6
OTHERS = 7.
IF sy-subrc <> 0.
* unable to fetch record details so return
RETURN.
ENDIF.
lt_orgman2[] = lt_orgman2_s[].
Now that we have all the details for the selected orders, we simple need to compare them
LOOP AT lt_header INTO lwa_header.
* Clear variables and looping tables
CLEAR:
lv_ship_to_v,
lv_sales_org_v,
lv_dis_channel_v,
lv_division_v,
lv_transaction_type_v,
lv_media_type_v,
lv_contact_v.
* Now that we have the partner list in lt_partner2
* fetch ship to party and check
READ TABLE lt_partner2
INTO lwa_partner2
WITH KEY partner_fct = lco_partner_2
ref_guid = lwa_header-guid.
IF sy-subrc EQ 0.
lv_ship_to_v = lwa_partner2-partner_no.
ENDIF.
IF lv_ship_to_c NE lv_ship_to_v.
CONTINUE.
ENDIF.
* fetch orgainzational data
READ TABLE lt_orgman2
INTO lwa_orgman2
WITH KEY sales_org_ori = lco_sori_a
ref_guid = lwa_header-guid.
IF sy-subrc EQ 0.
lv_sales_org_v = lwa_orgman2-sales_org_short.
lv_dis_channel_v = lwa_orgman2-dis_channel.
lv_division_v = lwa_orgman2-division.
ENDIF.
* if organizational data does not match check next record
IF lv_sales_org_c NE lv_sales_org_v
OR lv_dis_channel_c NE lv_dis_channel_v
OR lv_division_c NE lv_division_v.
CONTINUE.
ENDIF.
* Transaction type
lv_transaction_type_v = lwa_header-process_type.
* fetch media type
READ TABLE lt_activity2
INTO lwa_activity2
WITH KEY guid = lwa_header-guid.
IF sy-subrc EQ 0.
lv_media_type_v = lwa_activity2-category.
ENDIF.
* Compare and check items
IF lv_transaction_type_c = lv_transaction_type_v
AND lv_media_type_c = lv_media_type_v.
* When similar record exist in existing and current line items
* create table for current line items
REFRESH: lt_items, lt_items2.
LOOP AT lt_orderadm INTO lwa_orderadm.
lwa_items-ordered_prod = lwa_orderadm-ordered_prod.
CLEAR lwa_product_i.
READ TABLE lt_product_i
INTO lwa_product_i
WITH KEY guid = lwa_orderadm-guid.
IF sy-subrc EQ 0.
lwa_items-net_weight = lwa_product_i-net_weight.
ENDIF.
APPEND lwa_items TO lt_items.
ENDLOOP.
SORT lt_items.
* create table for existing line items
LOOP AT lt_orderadm2 INTO lwa_orderadm2.
IF lwa_orderadm2-header = lwa_header-guid.
lwa_items2-ordered_prod = lwa_orderadm2-ordered_prod.
CLEAR lwa_product_i2.
READ TABLE lt_product_i2
INTO lwa_product_i2
WITH KEY guid = lwa_orderadm2-guid.
IF sy-subrc EQ 0.
lwa_items2-net_weight = lwa_product_i2-net_weight.
ENDIF.
APPEND lwa_items2 TO lt_items2.
ENDIF.
ENDLOOP.
SORT lt_items2.
* now that we have the product and their quantities in sorted order
* we have to look for matching matching items
CLEAR lv_match.
LOOP AT lt_items INTO lwa_items.
READ TABLE lt_items2
TRANSPORTING NO FIELDS
WITH KEY ordered_prod = lwa_items-ordered_prod
net_weight = lwa_items-net_weight.
IF sy-subrc = 0.
lv_match = lv_match + 1.
DELETE lt_items2 INDEX sy-tabix.
ENDIF.
ENDLOOP.
* if there are more matching records lv_match than lwa_duplicate-items_chk
* or equal, then we say that its a duplicate record
IF lv_match GE lwa_duplicate-items_chk.
* check if contact person check is active, if yes then check contacts
IF lwa_duplicate-contact_per_chk IS NOT INITIAL.
* fetch contact data
READ TABLE lt_partner2
INTO lwa_partner2
WITH KEY partner_fct = lco_partner_15
ref_guid = lwa_header-guid.
IF sy-subrc EQ 0.
lv_contact_v = lwa_partner2-partner_no.
ENDIF.
IF lv_contact_c = lv_contact_v.
* show message
lv_message = text-t01.
REPLACE ALL OCCURRENCES OF '&' IN lv_message WITH lwa_header-object_id.
CALL FUNCTION 'POPUP_TO_CONFIRM'
EXPORTING
text_question = lv_message
display_cancel_button = ''
IMPORTING
answer = lv_answer.
IF lv_answer NE lco_ans_1.
RAISE do_not_save.
ENDIF.
RETURN.
ENDIF.
ELSE.
* when contact person check is not active
* show message
lv_message = text-t01.
REPLACE ALL OCCURRENCES OF '&' IN lv_message WITH lwa_header-object_id.
CALL FUNCTION 'POPUP_TO_CONFIRM'
EXPORTING
text_question = lv_message
display_cancel_button = ''
IMPORTING
answer = lv_answer.
IF lv_answer NE lco_ans_1.
RAISE do_not_save.
ENDIF.
RETURN.
ENDIF.
ENDIF.
ENDIF.
ENDLOOP.
As we can see that the program generates a popup message in case a possible duplicate order is found. The order number is reflected in the popup message asking whether to continue saving or not. In case we choose yes, the order is saved irrespective of possible duplicacy, else, an exception is raised and this causes the order not to be saved.
In this case, we get a popup in the SAP system while trying to save a new (or copied) order using CRMD_ORDER. If yes it will save, else it wont save