En el escenario de Master Data Hub, el sistema MDG es la «fuente de la verdad», los datos maestros se replican en los sistemas satelitales y se pueden ampliar allí, pero en general, la parte común no debe cambiarse localmente porque en la próxima replicación se sobrescribirá con la versión de MDG.
En algunos casos, puede suceder que un usuario travieso o una interfaz externa modifiquen parte de los datos que no deben cambiarse y, a menudo, es necesario detectar tal situación lo antes posible.
En el mercado existen herramientas para comparar los datos maestros entre sistemas, pero normalmente no son gratuitas, tienen una configuración compleja y lleva tiempo aprender cómo funcionan.
En este blog, ofrezco una solución sobre cómo comparar los datos maestros de materiales entre dos sistemas con una codificación relativamente corta (en total, alrededor de 1k líneas de ABAP) y «algunos clics del mouse». El código se puede implementar en el sistema Sandbox y está listo para comparar datos en cualquier sistema en el que configure una conexión RFC.
A Programa de recogida Delta se conecta a dos sistemas seleccionados (los destinos RFC deben configurarse en SM59) y lee las tablas de datos maestros de material del Sistema 1 y el Sistema 2 (con el módulo de función RFC estándar RFC_READ_TABLE; lea atentamente la nota 382318). Después de la comparación campo por campo, el delta calculado se muestra en la salida.
Como las tablas de datos maestros de materiales suelen ser enormes y la recopilación delta lleva tiempo, la salida del programa mencionado anteriormente se puede guardar en un Tabla Delta personalizada.
Para una buena visualización de la comparación, se debe crear una aplicación FPM simple (sin embargo, también puede usar MS Excel).
Como resultado obtenemos algo como lo siguiente:
Dónde Programa de recogida Delta parece:
y el Tabla Delta personalizada:
(He intentado proporcionar instrucciones detalladas para que quede claro para las personas con conocimientos básicos de ABAP)
Primero el Tabla Delta personalizada donde se almacenarán los registros delta recopilados, así que SE11 y cree una tabla como se muestra a continuación (para simplificar, he usado elementos de datos genéricos, pero, por supuesto, puede configurar los suyos propios con etiquetas bonitas)
Luego, la clase utilizada para recopilar los datos y calcular delta:
Simplemente cree una nueva clase ZCL_TABLES_COMPARE, cambie al modo «Basado en código fuente» y pegue el código a continuación (+ guardar y activar). Ver los comentarios en la llamada de ‘RFC_READ_TABLE’
CLASS zcl_tables_compare DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
TYPES:
ty_fields_t TYPE STANDARD TABLE OF rfc_db_fld WITH DEFAULT KEY .
TYPES:
BEGIN OF ty_log,
rkey TYPE ztab_compare-rkey,
tfname TYPE ztab_compare-tfname,
v1 TYPE ztab_compare-v1,
v2 TYPE ztab_compare-v2,
fdescr TYPE string,
END OF ty_log .
TYPES:
ty_log_t TYPE STANDARD TABLE OF ty_log WITH KEY rkey tfname .
CONSTANTS gc_key_delimeter TYPE char1 VALUE '/' ##NO_TEXT.
CLASS-METHODS run_comparison
IMPORTING
!iv_rfc_dest1 TYPE rfcdest
!iv_rfc_dest2 TYPE rfcdest
!iv_tabname TYPE tabname
!iv_key_fields TYPE string
!iv_excl_fields TYPE string OPTIONAL
!iv_range_from TYPE char100
!iv_range_to TYPE char100
!iv_ec_s1 TYPE abap_bool DEFAULT abap_true
!iv_ec_s2 TYPE abap_bool DEFAULT abap_true
RETURNING
VALUE(rt_result) TYPE ty_log_t .
CLASS-METHODS get_table_key_fields
IMPORTING
!iv_tabname TYPE tabname
RETURNING
VALUE(rv_result) TYPE string .
CLASS-METHODS get_table_field_descr
IMPORTING
!iv_tfname TYPE ztab_compare-tfname
RETURNING
VALUE(rv_result) TYPE string .
PROTECTED SECTION.
PRIVATE SECTION.
CLASS-METHODS is_status_different
IMPORTING
!iv_s1 TYPE any
!iv_s2 TYPE any
RETURNING
VALUE(rv_result) TYPE abap_bool .
CLASS-METHODS compare_tables
IMPORTING
!iv_tabname TYPE tabname
!ir_tab1 TYPE REF TO data
!ir_tab2 TYPE REF TO data
!iv_ec_s1 TYPE abap_bool
!iv_ec_s2 TYPE abap_bool
CHANGING
!ct_log TYPE ty_log_t .
CLASS-METHODS read_table
IMPORTING
!iv_rfc_dest TYPE rfcdest
!iv_tabname TYPE tabname
!iv_key_fields TYPE string
!iv_excl_fields TYPE string OPTIONAL
!iv_range_from TYPE char100 OPTIONAL
!iv_range_to TYPE char100 OPTIONAL
RETURNING
VALUE(rt_result) TYPE REF TO data .
ENDCLASS.
CLASS ZCL_TABLES_COMPARE IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_TABLES_COMPARE=>COMPARE_TABLES
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_TABNAME TYPE TABNAME
* | [--->] IR_TAB1 TYPE REF TO DATA
* | [--->] IR_TAB2 TYPE REF TO DATA
* | [--->] IV_EC_S1 TYPE ABAP_BOOL
* | [--->] IV_EC_S2 TYPE ABAP_BOOL
* | [<-->] CT_LOG TYPE TY_LOG_T
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD compare_tables.
DEFINE copy_tab.
ASSIGN &1->* TO <lt_data>.
IF sy-subrc EQ 0.
&2 = CORRESPONDING #( <lt_data> ).
SORT &2.
ENDIF.
END-OF-DEFINITION.
DEFINE check1.
IF sy-subrc EQ 0.
IF &1 NE &2.
LOOP AT lo_str_descr->components ASSIGNING <ls_comp>.
ASSIGN COMPONENT <ls_comp>-name OF STRUCTURE &1 TO <lv_value1>.
ASSIGN COMPONENT <ls_comp>-name OF STRUCTURE &2 TO <lv_value2>.
IF ';VPSTA;PSTAT;' CS |;{ <ls_comp>-name };|. " status field
IF abap_true EQ is_status_different( iv_s1 = <lv_value1> iv_s2 = <lv_value2> ).
APPEND VALUE #( rkey = lv_key tfname = |{ iv_tabname }-{ <ls_comp>-name }| v1 = <lv_value1> v2 = <lv_value2> ) TO ct_log.
ENDIF.
ELSE.
IF <lv_value1> NE <lv_value2>.
APPEND VALUE #( rkey = lv_key tfname = |{ iv_tabname }-{ <ls_comp>-name }| v1 = <lv_value1> v2 = <lv_value2> ) TO ct_log.
ENDIF.
ENDIF.
ENDLOOP.
ENDIF.
ELSE.
IF iv_ec_s1 EQ abap_true.
APPEND VALUE #( rkey = lv_key tfname = iv_tabname v1 = 'record exists' v2 = 'does not exist' ) TO ct_log. " key does not exist in System 2
ENDIF.
ENDIF.
END-OF-DEFINITION.
DEFINE check2.
IF sy-subrc NE 0.
APPEND VALUE #( rkey = lv_key tfname = iv_tabname v1 = 'does not exist' v2 = 'record exists' ) TO ct_log. " key does not exist in System 1
ENDIF.
END-OF-DEFINITION.
DATA: lt_mara1 TYPE STANDARD TABLE OF mara WITH KEY matnr,
lt_mara2 LIKE lt_mara1,
lt_marc1 TYPE STANDARD TABLE OF marc WITH KEY matnr werks,
lt_marc2 LIKE lt_marc1,
lt_makt1 TYPE STANDARD TABLE OF makt WITH KEY matnr spras,
lt_makt2 LIKE lt_makt1,
lt_mard1 TYPE STANDARD TABLE OF mard WITH KEY matnr werks lgort,
lt_mard2 LIKE lt_mard1,
lt_marm1 TYPE STANDARD TABLE OF marm WITH KEY matnr meinh,
lt_marm2 LIKE lt_marm1,
lt_mbew1 TYPE STANDARD TABLE OF mbew WITH KEY matnr bwkey bwtar,
lt_mbew2 LIKE lt_mbew1,
lt_mean1 TYPE STANDARD TABLE OF mean WITH KEY matnr meinh ean11, "this one is special case - LFNUM may differ between sytstems
lt_mean2 LIKE lt_mean1,
lt_mlgn1 TYPE STANDARD TABLE OF mlgn WITH KEY matnr lgnum,
lt_mlgn2 LIKE lt_mlgn1,
lt_mlgt1 TYPE STANDARD TABLE OF mlgt WITH KEY matnr lgnum lgtyp,
lt_mlgt2 LIKE lt_mlgt1,
lt_mvke1 TYPE STANDARD TABLE OF mvke WITH KEY matnr vkorg vtweg,
lt_mvke2 LIKE lt_mvke1,
lt_qmat1 TYPE STANDARD TABLE OF qmat WITH KEY art matnr werks,
lt_qmat2 LIKE lt_qmat1,
lo_str_descr TYPE REF TO cl_abap_structdescr,
lv_key TYPE string.
FIELD-SYMBOLS: <lt_data> TYPE ANY TABLE,
<ls_comp> TYPE abap_compdescr,
<lv_value1> TYPE any,
<lv_value2> TYPE any.
lo_str_descr = CAST #( cl_abap_structdescr=>describe_by_name( iv_tabname ) ).
CASE iv_tabname.
WHEN 'MARA'.
copy_tab ir_tab1 lt_mara1.
copy_tab ir_tab2 lt_mara2.
LOOP AT lt_mara1 ASSIGNING FIELD-SYMBOL(<ls_mara1>).
lv_key = |{ <ls_mara1>-matnr ALPHA = OUT }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mara2 WITH KEY matnr = <ls_mara1>-matnr ASSIGNING FIELD-SYMBOL(<ls_mara2>) BINARY SEARCH.
check1 <ls_mara1> <ls_mara2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mara2 ASSIGNING <ls_mara2>.
lv_key = |{ <ls_mara1>-matnr ALPHA = OUT }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mara1 WITH KEY matnr = <ls_mara2>-matnr TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MARC'.
copy_tab ir_tab1 lt_marc1.
copy_tab ir_tab2 lt_marc2.
LOOP AT lt_marc1 ASSIGNING FIELD-SYMBOL(<ls_marc1>).
lv_key = |{ <ls_marc1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_marc1>-werks }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_marc2 WITH KEY matnr = <ls_marc1>-matnr werks = <ls_marc1>-werks ASSIGNING FIELD-SYMBOL(<ls_marc2>) BINARY SEARCH.
check1 <ls_marc1> <ls_marc2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_marc2 ASSIGNING <ls_marc2>.
lv_key = |{ <ls_marc2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_marc2>-werks }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_marc1 WITH KEY matnr = <ls_marc2>-matnr werks = <ls_marc2>-werks TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MAKT'.
copy_tab ir_tab1 lt_makt1.
copy_tab ir_tab2 lt_makt2.
LOOP AT lt_makt1 ASSIGNING FIELD-SYMBOL(<ls_makt1>).
lv_key = |{ <ls_makt1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_makt1>-spras }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_makt2 WITH KEY matnr = <ls_makt1>-matnr spras = <ls_makt1>-spras ASSIGNING FIELD-SYMBOL(<ls_makt2>) BINARY SEARCH.
check1 <ls_makt1> <ls_makt2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_makt2 ASSIGNING <ls_makt2>.
lv_key = |{ <ls_makt2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_makt2>-spras }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_makt1 WITH KEY matnr = <ls_makt2>-matnr spras = <ls_makt2>-spras TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MARD'.
copy_tab ir_tab1 lt_mard1.
copy_tab ir_tab2 lt_mard2.
LOOP AT lt_mard1 ASSIGNING FIELD-SYMBOL(<ls_mard1>).
lv_key = |{ <ls_mard1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mard1>-werks }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mard1>-lgort }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mard2 WITH KEY matnr = <ls_mard1>-matnr werks = <ls_mard1>-werks lgort = <ls_mard1>-lgort ASSIGNING FIELD-SYMBOL(<ls_mard2>) BINARY SEARCH.
check1 <ls_mard1> <ls_mard2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mard2 ASSIGNING <ls_mard2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_mard2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mard2>-werks }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mard2>-lgort }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mard1 WITH KEY matnr = <ls_mard2>-matnr werks = <ls_mard2>-werks lgort = <ls_mard2>-lgort TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MARM'.
copy_tab ir_tab1 lt_marm1.
copy_tab ir_tab2 lt_marm2.
LOOP AT lt_marm1 ASSIGNING FIELD-SYMBOL(<ls_marm1>).
lv_key = |{ <ls_marm1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_marm1>-meinh }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_marm2 WITH KEY matnr = <ls_marm1>-matnr meinh = <ls_marm1>-meinh ASSIGNING FIELD-SYMBOL(<ls_marm2>) BINARY SEARCH.
check1 <ls_marm1> <ls_marm2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_marm2 ASSIGNING <ls_marm2>.
lv_key = |{ <ls_marm2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_marm2>-meinh }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_marm1 WITH KEY matnr = <ls_marm2>-matnr meinh = <ls_marm2>-meinh TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MBEW'.
copy_tab ir_tab1 lt_mbew1.
copy_tab ir_tab2 lt_mbew2.
LOOP AT lt_mbew1 ASSIGNING FIELD-SYMBOL(<ls_mbew1>).
lv_key = |{ <ls_mbew1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mbew1>-bwkey }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mbew1>-bwtar }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mbew2 WITH KEY matnr = <ls_mbew1>-matnr bwkey = <ls_mbew1>-bwkey bwtar = <ls_mbew1>-bwtar ASSIGNING FIELD-SYMBOL(<ls_mbew2>) BINARY SEARCH.
check1 <ls_mbew1> <ls_mbew2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mbew2 ASSIGNING <ls_mbew2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_mbew2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mbew2>-bwkey }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mbew2>-bwtar }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mbew1 WITH KEY matnr = <ls_mbew2>-matnr bwkey = <ls_mbew2>-bwkey bwtar = <ls_mbew2>-bwtar TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MEAN'.
copy_tab ir_tab1 lt_mean1.
copy_tab ir_tab2 lt_mean2.
LOOP AT lt_mean1 ASSIGNING FIELD-SYMBOL(<ls_mean1>).
lv_key = |{ <ls_mean1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mean1>-meinh }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mean1>-ean11 }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mean2 WITH KEY matnr = <ls_mean1>-matnr meinh = <ls_mean1>-meinh ean11 = <ls_mean1>-ean11 ASSIGNING FIELD-SYMBOL(<ls_mean2>) BINARY SEARCH.
CLEAR: <ls_mean1>-lfnum, <ls_mean2>-lfnum. " we don't want to compare this field, it may be different in both systems
check1 <ls_mean1> <ls_mean2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mean2 ASSIGNING <ls_mean2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_mean2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mean2>-meinh }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mean2>-ean11 }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mean1 WITH KEY matnr = <ls_mean2>-matnr meinh = <ls_mean2>-meinh ean11 = <ls_mean2>-ean11 TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MLGN'.
copy_tab ir_tab1 lt_mlgn1.
copy_tab ir_tab2 lt_mlgn2.
LOOP AT lt_mlgn1 ASSIGNING FIELD-SYMBOL(<ls_mlgn1>).
lv_key = |{ <ls_mlgn1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgn1>-lgnum }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mlgn2 WITH KEY matnr = <ls_mlgn1>-matnr lgnum = <ls_mlgn1>-lgnum ASSIGNING FIELD-SYMBOL(<ls_mlgn2>) BINARY SEARCH.
check1 <ls_mlgn1> <ls_mlgn2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mlgn2 ASSIGNING <ls_mlgn2>.
lv_key = |{ <ls_mlgn2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgn2>-lgnum }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mlgn1 WITH KEY matnr = <ls_mlgn2>-matnr lgnum = <ls_mlgn2>-lgnum TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MLGT'.
copy_tab ir_tab1 lt_mlgt1.
copy_tab ir_tab2 lt_mlgt2.
LOOP AT lt_mlgt1 ASSIGNING FIELD-SYMBOL(<ls_mlgt1>).
lv_key = |{ <ls_mlgt1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgt1>-lgnum }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgt1>-lgtyp }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mlgt2 WITH KEY matnr = <ls_mlgt1>-matnr lgnum = <ls_mlgt1>-lgnum lgtyp = <ls_mlgt1>-lgtyp ASSIGNING FIELD-SYMBOL(<ls_mlgt2>) BINARY SEARCH.
check1 <ls_mlgt1> <ls_mlgt2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mlgt2 ASSIGNING <ls_mlgt2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_mlgt2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgt2>-lgnum }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgt2>-lgtyp }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mlgt1 WITH KEY matnr = <ls_mlgt2>-matnr lgnum = <ls_mlgt2>-lgnum lgtyp = <ls_mlgt2>-lgtyp TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MVKE'.
copy_tab ir_tab1 lt_mvke1.
copy_tab ir_tab2 lt_mvke2.
LOOP AT lt_mvke1 ASSIGNING FIELD-SYMBOL(<ls_mvke1>).
lv_key = |{ <ls_mvke1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mvke1>-vkorg }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mvke1>-vtweg }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mvke2 WITH KEY matnr = <ls_mvke1>-matnr vkorg = <ls_mvke1>-vkorg vtweg = <ls_mvke1>-vtweg ASSIGNING FIELD-SYMBOL(<ls_mvke2>) BINARY SEARCH.
check1 <ls_mvke1> <ls_mvke2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mvke2 ASSIGNING <ls_mvke2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_mvke2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mvke2>-vkorg }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mvke2>-vtweg }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mvke1 WITH KEY matnr = <ls_mvke2>-matnr vkorg = <ls_mvke2>-vkorg vtweg = <ls_mvke2>-vtweg TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'QMAT'.
copy_tab ir_tab1 lt_qmat1.
copy_tab ir_tab2 lt_qmat2.
LOOP AT lt_qmat1 ASSIGNING FIELD-SYMBOL(<ls_qmat1>).
lv_key = |{ <ls_qmat1>-art }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_qmat1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_qmat1>-werks }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_qmat2 WITH KEY art = <ls_qmat1>-art matnr = <ls_qmat1>-matnr werks = <ls_qmat1>-werks ASSIGNING FIELD-SYMBOL(<ls_qmat2>) BINARY SEARCH.
check1 <ls_qmat1> <ls_qmat2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_qmat2 ASSIGNING <ls_qmat2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_qmat2>-art }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_qmat2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_qmat2>-werks }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_qmat1 WITH KEY art = <ls_qmat2>-art matnr = <ls_qmat2>-matnr werks = <ls_qmat2>-werks TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
ENDCASE.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_TABLES_COMPARE=>GET_TABLE_FIELD_DESCR
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_TFNAME TYPE ZTAB_COMPARE-TFNAME
* | [<-()] RV_RESULT TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_table_field_descr.
TYPES: BEGIN OF lty_tf_name,
tfname TYPE ztab_compare-tfname,
fdescr TYPE string,
END OF lty_tf_name.
STATICS: st_fields TYPE HASHED TABLE OF lty_tf_name WITH UNIQUE KEY tfname,
sv_tables TYPE string.
READ TABLE st_fields ASSIGNING FIELD-SYMBOL(<ls_fld>) WITH TABLE KEY tfname = iv_tfname.
IF sy-subrc EQ 0.
rv_result = <ls_fld>-fdescr.
RETURN.
ENDIF.
DATA(lv_tname) = substring_before( val = iv_tfname sub = '-' ).
IF lv_tname IS INITIAL OR sv_tables CS lv_tname.
RETURN.
ENDIF.
sv_tables = |{ sv_tables };{ lv_tname }|.
DATA(lo_struc_descr) = CAST cl_abap_structdescr( cl_abap_structdescr=>describe_by_name( lv_tname ) ).
LOOP AT lo_struc_descr->get_ddic_field_list( p_langu = sy-langu ) ASSIGNING FIELD-SYMBOL(<ls_comp>).
INSERT VALUE #( tfname = |{ lv_tname }-{ <ls_comp>-fieldname }| fdescr = <ls_comp>-scrtext_l ) INTO TABLE st_fields.
ENDLOOP.
rv_result = VALUE #( st_fields[ tfname = iv_tfname ]-fdescr OPTIONAL ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_TABLES_COMPARE=>GET_TABLE_KEY_FIELDS
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_TABNAME TYPE TABNAME
* | [<-()] RV_RESULT TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_table_key_fields.
SELECT fieldname INTO TABLE @DATA(lt_key_fields) FROM dd03l
WHERE tabname EQ @iv_tabname AND keyflag EQ @abap_true AND fieldname NE 'MANDT'
ORDER BY position.
CONCATENATE LINES OF lt_key_fields INTO rv_result SEPARATED BY ';'.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_TABLES_COMPARE=>IS_STATUS_DIFFERENT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_S1 TYPE ANY
* | [--->] IV_S2 TYPE ANY
* | [<-()] RV_RESULT TYPE ABAP_BOOL
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD is_status_different.
DATA: lv_pos TYPE i VALUE 0,
lv_len TYPE i.
rv_result = abap_false.
lv_len = strlen( iv_s1 ).
IF lv_len NE strlen( iv_s2 ).
rv_result = abap_true. RETURN.
ENDIF.
DO.
IF lv_pos GE lv_len.
RETURN.
ENDIF.
IF iv_s2 NS iv_s1+lv_pos(1).
rv_result = abap_true. RETURN.
ENDIF.
lv_pos = lv_pos + 1.
ENDDO.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_TABLES_COMPARE=>READ_TABLE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_RFC_DEST TYPE RFCDEST
* | [--->] IV_TABNAME TYPE TABNAME
* | [--->] IV_KEY_FIELDS TYPE STRING
* | [--->] IV_EXCL_FIELDS TYPE STRING(optional)
* | [--->] IV_RANGE_FROM TYPE CHAR100(optional)
* | [--->] IV_RANGE_TO TYPE CHAR100(optional)
* | [<-()] RT_RESULT TYPE REF TO DATA
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD read_table.
DATA: lt_options TYPE STANDARD TABLE OF rfc_db_opt WITH DEFAULT KEY,
lt_all_fields TYPE ty_fields_t,
lt_key_fields TYPE ty_fields_t,
lt_sel_fields TYPE ty_fields_t,
lt_pkg_fields TYPE ty_fields_t,
lt_data TYPE STANDARD TABLE OF tab512 WITH DEFAULT KEY,
lt_comp TYPE cl_abap_structdescr=>component_table,
lr_tab TYPE REF TO data,
lr_wa TYPE REF TO data,
lt_tr_fields TYPE ty_fields_t,
lv_rowcount TYPE soid-accnt VALUE 0.
FIELD-SYMBOLS: <lt_out> TYPE INDEX TABLE.
DATA: lt_packages TYPE stringtab,
lv_offset TYPE i.
" first compare if structures of table are the same in both systems
CALL FUNCTION 'RFC_READ_TABLE'
DESTINATION iv_rfc_dest
EXPORTING
query_table = iv_tabname
no_data = abap_true
TABLES
fields = lt_all_fields
EXCEPTIONS
OTHERS = 7.
" remove excluded fields and get key fields
LOOP AT lt_all_fields ASSIGNING FIELD-SYMBOL(<ls_field>).
IF iv_key_fields CS <ls_field>-fieldname.
APPEND <ls_field> TO lt_key_fields.
CONTINUE.
ENDIF.
IF iv_excl_fields CS <ls_field>-fieldname.
CONTINUE.
ENDIF.
APPEND <ls_field> TO lt_sel_fields.
ENDLOOP.
ASSERT lt_key_fields IS NOT INITIAL.
" ... and prepare selection range for WHERE clause
DATA(lv_first_key) = VALUE #( lt_key_fields[ 1 ]-fieldname ).
IF iv_range_from IS INITIAL AND iv_range_to IS INITIAL.
lt_options = VALUE #( ( text = |1 EQ 1| ) ). " select all but with some fuse
lv_rowcount = 100000.
ELSEIF iv_range_from IS NOT INITIAL AND iv_range_to IS NOT INITIAL.
lt_options = VALUE #( ( text = |{ lv_first_key } BETWEEN '{ iv_range_from }' AND '{ iv_range_to }'| ) ).
ELSEIF iv_range_from IS NOT INITIAL.
lt_options = VALUE #( ( text = |{ lv_first_key } EQ '{ iv_range_from }'| ) ).
ELSE. " upper limit provided without lower
ASSERT 1 EQ 2.
ENDIF.
*>>> prepare output table
LOOP AT lt_all_fields ASSIGNING <ls_field>.
APPEND VALUE #( name = <ls_field>-fieldname type = cl_abap_elemdescr=>get_c( p_length = CONV #( <ls_field>-length ) ) ) TO lt_comp.
ENDLOOP.
DATA(lo_tab_struc) = cl_abap_structdescr=>create( p_components = lt_comp ).
DATA(lt_ddic) = CAST cl_abap_structdescr( cl_abap_structdescr=>describe_by_name( iv_tabname ) )->get_ddic_field_list( ).
SORT lt_ddic BY fieldname.
CREATE DATA lr_wa TYPE HANDLE lo_tab_struc.
ASSIGN lr_wa->* TO FIELD-SYMBOL(<ls_wa_out>).
DATA(lt_keys) = VALUE abap_table_keydescr_tab( ( name="MAIN"
is_primary = abap_true
is_unique = abap_true
access_kind = cl_abap_tabledescr=>tablekind_sorted
key_kind = cl_abap_tabledescr=>keydefkind_user "keydefkind_user
components = CORRESPONDING #( lt_key_fields MAPPING name = fieldname ) ) ).
TRY.
DATA(lo_tab_descr) = cl_abap_tabledescr=>create_with_keys( p_line_type = lo_tab_struc
p_keys = lt_keys ).
CATCH cx_sy_table_creation INTO DATA(lx_error).
RETURN.
ENDTRY.
CREATE DATA lr_tab TYPE HANDLE lo_tab_descr.
ASSIGN lr_tab->* TO <lt_out>.
*<<<
" collect data in packages up to 512 characters
DO.
IF lt_sel_fields IS INITIAL.
EXIT. " DO..ENDDO
ENDIF.
CLEAR: lv_offset, lt_pkg_fields, lt_tr_fields, lt_data.
LOOP AT lt_key_fields ASSIGNING <ls_field>.
lv_offset = lv_offset + <ls_field>-length.
APPEND <ls_field> TO lt_pkg_fields.
ENDLOOP.
LOOP AT lt_sel_fields ASSIGNING <ls_field>.
lv_offset = lv_offset + <ls_field>-length.
IF lv_offset GE 512.
EXIT. "loop
ENDIF.
APPEND <ls_field> TO: lt_pkg_fields, lt_tr_fields.
DELETE lt_sel_fields USING KEY loop_key.
ENDLOOP.
CALL FUNCTION 'RFC_READ_TABLE'
DESTINATION iv_rfc_dest
EXPORTING
query_table = iv_tabname
rowcount = lv_rowcount
TABLES
options = lt_options
fields = lt_pkg_fields
data = lt_data
EXCEPTIONS
OTHERS = 7.
IF sy-subrc <> 0.
* Implement suitable error handling here
ENDIF.
LOOP AT lt_data ASSIGNING FIELD-SYMBOL(<ls_data>).
CLEAR <ls_wa_out>.
LOOP AT lt_pkg_fields ASSIGNING <ls_field>.
" the RFC FM is a bit crappy, when e.g. field is of type P length 3 dec 1 (example MDA MARC-LGRAD for 23430/0400)
" it tries to convert the 99.0 into CHAR3 and in result there is set '*.0' there !?!?
" in fact the FM is obsolete (see note 382318), see also something new in notes 2246160 and 3139000
ASSIGN COMPONENT <ls_field>-fieldname OF STRUCTURE <ls_wa_out> TO FIELD-SYMBOL(<lv_fvalue>).
IF sy-subrc EQ 0.
READ TABLE lt_ddic ASSIGNING FIELD-SYMBOL(<ls_ddic>) WITH KEY fieldname = <ls_field>-fieldname.
IF sy-subrc EQ 0 AND <ls_ddic>-inttype EQ 'P'.
IF <ls_data>-wa+<ls_field>-offset(<ls_field>-length) CS '*'.
<lv_fvalue> = '6.9'. " set here something not initial to at least detect comparison with 0
ELSE.
<lv_fvalue> = <ls_data>-wa+<ls_field>-offset(<ls_field>-length).
ENDIF.
ELSE.
<lv_fvalue> = <ls_data>-wa+<ls_field>-offset(<ls_field>-length).
ENDIF.
ENDIF.
ENDLOOP.
READ TABLE <lt_out> FROM <ls_wa_out> ASSIGNING FIELD-SYMBOL(<ls_found>).
IF sy-subrc EQ 0.
LOOP AT lt_tr_fields ASSIGNING <ls_field>.
ASSIGN COMPONENT <ls_field>-fieldname OF STRUCTURE <ls_wa_out> TO FIELD-SYMBOL(<lv_src>).
IF sy-subrc EQ 0.
ASSIGN COMPONENT <ls_field>-fieldname OF STRUCTURE <ls_found> TO FIELD-SYMBOL(<lv_dst>).
IF sy-subrc EQ 0.
<lv_dst> = <lv_src>.
ENDIF.
ENDIF.
ENDLOOP.
ELSE.
INSERT <ls_wa_out> INTO TABLE <lt_out>.
ENDIF.
ENDLOOP.
ENDDO.
rt_result = lr_tab.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_TABLES_COMPARE=>RUN_COMPARISON
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_RFC_DEST1 TYPE RFCDEST
* | [--->] IV_RFC_DEST2 TYPE RFCDEST
* | [--->] IV_TABNAME TYPE TABNAME
* | [--->] IV_KEY_FIELDS TYPE STRING
* | [--->] IV_EXCL_FIELDS TYPE STRING(optional)
* | [--->] IV_RANGE_FROM TYPE CHAR100
* | [--->] IV_RANGE_TO TYPE CHAR100
* | [--->] IV_EC_S1 TYPE ABAP_BOOL (default =ABAP_TRUE)
* | [--->] IV_EC_S2 TYPE ABAP_BOOL (default =ABAP_TRUE)
* | [<-()] RT_RESULT TYPE TY_LOG_T
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD run_comparison.
" get data from RFC1
DATA(lr_data1) = read_table( EXPORTING iv_rfc_dest = iv_rfc_dest1
iv_tabname = iv_tabname
iv_key_fields = iv_key_fields
iv_excl_fields = iv_excl_fields
iv_range_from = iv_range_from
iv_range_to = iv_range_to ).
" get data from RFC2
DATA(lr_data2) = read_table( EXPORTING iv_rfc_dest = iv_rfc_dest2
iv_tabname = iv_tabname
iv_key_fields = iv_key_fields
iv_excl_fields = iv_excl_fields
iv_range_from = iv_range_from
iv_range_to = iv_range_to ).
compare_tables( EXPORTING iv_tabname = iv_tabname
ir_tab1 = lr_data1
ir_tab2 = lr_data2
iv_ec_s1 = iv_ec_s1
iv_ec_s2 = iv_ec_s2
CHANGING ct_log = rt_result ).
ENDMETHOD.
ENDCLASS.
Ahora el programa que recoge el delta:
Cree un nuevo programa ejecutable ZPST_TEST_COMPARE con el siguiente código (el nombre del programa no se usa en ninguna parte, así que configúrelo como desee):
*&---------------------------------------------------------------------*
*& Report ZPST_TEST_COMPARE
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zpst_test_compare.
DATA: gr_salv TYPE REF TO cl_salv_table,
gt_log TYPE zcl_tables_compare=>ty_log_t.
PARAMETERS: p_rfc1 TYPE rfcdest DEFAULT 'MDGCLNT100',
p_rfc2 TYPE rfcdest DEFAULT 'ECCCLNT100'.
SELECT-OPTIONS: s_matnr FOR ('MATNR') NO-EXTENSION OBLIGATORY DEFAULT '000000000000042373' TO '000000000000044335',
s_fexcl FOR ('USMD_FIELDNAME') NO INTERVALS.
PARAMETERS: p_mara TYPE c AS CHECKBOX DEFAULT 'X',
p_marc TYPE c AS CHECKBOX,
p_makt TYPE c AS CHECKBOX,
p_mard TYPE c AS CHECKBOX,
p_marm TYPE c AS CHECKBOX,
p_mbew TYPE c AS CHECKBOX,
p_mean TYPE c AS CHECKBOX,
p_mlgn TYPE c AS CHECKBOX,
p_mlgt TYPE c AS CHECKBOX,
* p_mpgd TYPE c AS CHECKBOX,
* p_mpop TYPE c AS CHECKBOX,
p_mvke TYPE c AS CHECKBOX,
p_qmat TYPE c AS CHECKBOX.
* p_mdma TYPE c AS CHECKBOX,
SELECTION-SCREEN SKIP.
PARAMETERS: p_s1_ec TYPE abap_bool AS CHECKBOX DEFAULT abap_true,
p_s2_ec TYPE abap_bool AS CHECKBOX DEFAULT abap_false.
INCLUDE zpst_test_compare_forms.
*-----------------------------------------------------------------------------------
START-OF-SELECTION.
IF p_mara EQ abap_true.
PERFORM compare_table USING 'MARA' CHANGING gt_log.
ENDIF.
IF p_marc EQ abap_true.
PERFORM compare_table USING 'MARC' CHANGING gt_log.
ENDIF.
IF p_makt EQ abap_true.
PERFORM compare_table USING 'MAKT' CHANGING gt_log.
ENDIF.
IF p_mard EQ abap_true.
PERFORM compare_table USING 'MARD' CHANGING gt_log.
ENDIF.
IF p_marm EQ abap_true.
PERFORM compare_table USING 'MARM' CHANGING gt_log.
ENDIF.
IF p_mbew EQ abap_true.
PERFORM compare_table USING 'MBEW' CHANGING gt_log.
ENDIF.
IF p_mean EQ abap_true.
PERFORM compare_table USING 'MEAN' CHANGING gt_log.
ENDIF.
IF p_mlgn EQ abap_true.
PERFORM compare_table USING 'MLGN' CHANGING gt_log.
ENDIF.
IF p_mlgt EQ abap_true.
PERFORM compare_table USING 'MLGT' CHANGING gt_log.
ENDIF.
IF p_mvke EQ abap_true.
PERFORM compare_table USING 'MVKE' CHANGING gt_log.
ENDIF.
IF p_qmat EQ abap_true.
PERFORM compare_table USING 'QMAT' CHANGING gt_log.
ENDIF.
SORT gt_log BY rkey tfname.
*-----------------------------------------------------------------------------------
END-OF-SELECTION.
DATA(gv_repid) = sy-repid.
TRY.
cl_salv_table=>factory( IMPORTING r_salv_table = gr_salv
CHANGING t_table = gt_log ).
CATCH cx_salv_msg. "#EC NO_HANDLER
RETURN.
ENDTRY.
gr_salv->set_screen_status( pfstatus="SALV_STANDARD"
report = gv_repid
set_functions = cl_salv_table=>c_functions_all ).
PERFORM salv_set_columns USING gr_salv.
gr_salv->get_layout( )->set_key( VALUE #( report = gv_repid ) ).
gr_salv->get_layout( )->set_default( abap_true ).
gr_salv->get_layout( )->set_save_restriction( if_salv_c_layout=>restrict_none ).
gr_salv->get_functions( )->set_print_preview( abap_false ).
DATA(gr_events) = NEW lcl_handle_events( ). "#EC NEEDED
SET HANDLER gr_events->on_user_command FOR gr_salv->get_event( ).
gr_salv->display( ).
Haga doble clic:
Y crea el include con el siguiente código:
*&---------------------------------------------------------------------*
*& Include ZPST_TEST_COMPARE_FORMS
*&---------------------------------------------------------------------*
CLASS lcl_handle_events DEFINITION.
PUBLIC SECTION.
METHODS:
on_user_command FOR EVENT added_function OF cl_salv_events IMPORTING e_salv_function.
ENDCLASS.
CLASS lcl_handle_events IMPLEMENTATION.
METHOD on_user_command.
PERFORM show_function_info USING e_salv_function.
ENDMETHOD. "on_user_command
ENDCLASS.
*-----------------------------------------------------------------------------------
FORM salv_set_columns USING ir_alv TYPE REF TO cl_salv_table .
DATA: lo_col TYPE REF TO cl_salv_column_table.
DATA(lo_columns) = ir_alv->get_columns( ).
lo_columns->set_optimize( ).
TRY.
" set Code Text columns names
DATA(lt_columns) = lo_columns->get( ).
LOOP AT lt_columns ASSIGNING FIELD-SYMBOL(<ls_column>).
lo_col = CAST #( <ls_column>-r_column ).
CASE <ls_column>-columnname.
WHEN 'RKEY'. lo_col->set_long_text( CONV #( 'Key' ) ).
WHEN 'TFNAME'. lo_col->set_long_text( CONV #( 'Table-Field Name' ) ).
WHEN 'V1'. lo_col->set_long_text( CONV #( |Value in { p_rfc1 }| ) ).
WHEN 'V2'. lo_col->set_long_text( CONV #( |Value in { p_rfc2 }| ) ).
WHEN 'FDESCR'. lo_col->set_long_text( CONV #( 'Field Description' ) ).
ENDCASE.
ENDLOOP.
CATCH cx_salv_not_found.
RETURN.
ENDTRY.
ENDFORM.
FORM show_function_info USING i_function TYPE salv_de_function.
DATA: ls_ztab_compare TYPE ztab_compare,
lt_ztab_compare TYPE STANDARD TABLE OF ztab_compare WITH KEY tfname rkey.
CASE i_function.
WHEN 'SAVEDB'.
IF gt_log IS NOT INITIAL.
LOOP AT gt_log ASSIGNING FIELD-SYMBOL(<ls_log>).
ls_ztab_compare = CORRESPONDING #( <ls_log> ).
ls_ztab_compare-v1 = condense( val = ls_ztab_compare-v1 from = '' ).
ls_ztab_compare-v2 = condense( val = ls_ztab_compare-v2 from = '' ).
IF ls_ztab_compare-v1 EQ 'does not exist'. CLEAR ls_ztab_compare-v1. ENDIF.
IF ls_ztab_compare-v2 EQ 'does not exist'. CLEAR ls_ztab_compare-v2. ENDIF.
IF ls_ztab_compare-v1 EQ 'record exists'. ls_ztab_compare-v1 = abap_true. ENDIF.
IF ls_ztab_compare-v2 EQ 'record exists'. ls_ztab_compare-v2 = abap_true. ENDIF.
APPEND ls_ztab_compare TO lt_ztab_compare.
ENDLOOP.
MODIFY ztab_compare FROM TABLE lt_ztab_compare.
COMMIT WORK.
MESSAGE s208(00) WITH 'Data saved in ZTAB_COMPARE'.
ENDIF.
WHEN 'CLEARDB'.
DELETE FROM ztab_compare.
COMMIT WORK.
MESSAGE s208(00) WITH 'Content of ZTAB_COMPARE deleted'.
ENDCASE.
ENDFORM.
*-----------------------------------------------------------------------------------
FORM get_excluded USING iv_tabname TYPE tabname
CHANGING cv_excluded TYPE string.
CLEAR cv_excluded.
LOOP AT s_fexcl INTO DATA(ls_so) WHERE sign EQ 'I' AND option EQ 'EQ'.
IF ls_so-low CP |{ iv_tabname }-*|.
IF cv_excluded IS INITIAL.
cv_excluded = substring_after( val = ls_so-low sub = |{ iv_tabname }-| ).
ELSE.
cv_excluded = cv_excluded && ';' && substring_after( val = ls_so-low sub = |{ iv_tabname }-| ).
ENDIF.
ENDIF.
ENDLOOP.
ENDFORM.
FORM compare_table USING iv_tabname TYPE tabname
CHANGING ct_log TYPE zcl_tables_compare=>ty_log_t.
DATA: lv_excluded TYPE string,
lt_log TYPE zcl_tables_compare=>ty_log_t.
PERFORM get_excluded USING iv_tabname CHANGING lv_excluded.
lt_log = zcl_tables_compare=>run_comparison( iv_rfc_dest1 = p_rfc1
iv_rfc_dest2 = p_rfc2
iv_tabname = iv_tabname
iv_key_fields = zcl_tables_compare=>get_table_key_fields( iv_tabname )
iv_excl_fields = lv_excluded
iv_range_from = VALUE #( s_matnr[ 1 ]-low OPTIONAL )
iv_range_to = VALUE #( s_matnr[ 1 ]-high OPTIONAL )
iv_ec_s1 = p_s1_ec
iv_ec_s2 = p_s2_ec ).
"update_field_descriptions
LOOP AT lt_log ASSIGNING FIELD-SYMBOL(<ls_log>).
<ls_log>-fdescr = zcl_tables_compare=>get_table_field_descr( <ls_log>-tfname ).
ENDLOOP.
APPEND LINES OF lt_log TO ct_log.
ENDFORM.
Crear estado de GUI SALV_STANDARD (pantalla normal):
Copie de la plantilla como a continuación:
Usar estado de plantilla: SALV_STANDARD del programa SALV_DEMO_TABLE_FUNCTIONS
Elimine MYFUNCTION y ajuste los tres códigos de función:
(Guardar y activar)
Luego los textos de selección del programa:
Después de la activación, el programa debería funcionar, ahora unas pocas palabras sobre los parámetros de selección
Sistema RFC 1 y 2 – estas son conexiones RFC configuradas en SM59 que apuntan a dos sistemas desde los cuales desea comparar los datos maestros de materiales. Si las conexiones se configuran sin usuario/contraseña (recomendado), cada vez que se ejecute el programa se mostrará una ventana de inicio de sesión (comportamiento estándar con llamada RFC). Por lo general, seleccione el Hub de datos como Sistema 1 y como Sistema 2 el sistema de destino de datos.
En Rango de número de material proporcione la gama de materiales que desea comparar. Se recomienda probar primero en un rango pequeño (por ejemplo, 100 materiales) qué tan rápido se procesan, la velocidad dependerá del tamaño de las subtablas (MARC, MVKE, …). Normalmente, ejecuta la comparación en paquetes de unos pocos miles de registros y guarda cada resultado de la comparación en la tabla Delta personalizada con el botón «Guardar base de datos» (ver más abajo).
Nombres de campos excluidos – aquí enumera los campos que no desea comparar, estos serán «creados en», «creados por» y todos los campos que son «editables» en el sistema satelital (como pronósticos, costos, período/año actual, existencias, etc.). Por lo general, puede identificar dichos campos ejecutando la colección delta sin excluir ningún campo (en una pequeña cantidad de registros) y verificando qué campos tienen muchos registros delta. La forma del campo a configurar aquí es
Casillas de verificación de nombres de tabla – aquí selecciona qué tablas desea comparar (el programa se puede ampliar fácilmente con otras tablas de materiales como MPOP, MDMA o extensiones personalizadas)
Las dos últimas casillas de verificación se utilizan para recopilar registros que existen en un sistema pero no en el otro. De manera predeterminada, el primero está activado (queremos averiguar si todos los registros del sistema de origen se replicaron en el destino) y el segundo está desactivado (en el sistema de destino, el material se puede mejorar con plantas adicionales, organizaciones de ventas, etc. si hay muchos). de dichas mejoras, la tabla delta puede crecer rápidamente si tiene la bandera activada)
Después de ejecutar el programa, debería obtener un resultado similar al siguiente
Con los dos botones señalados arriba puede: guardar el resultado en la tabla ZTAB_COMPARE (añadiendo los registros recopilados a los existentes) y limpiar toda la tabla ZTAB_COMPARE.
Ya en este punto, puede exportar los registros recopilados de Custom Delta Table a xlsx y formatearlos según sea necesario. Sin embargo, para aquellos a quienes les gusta FPM pero no saben cómo usarlo, describo a continuación los pasos para crear un informe simple.
Primero cree una nueva clase ZCL_TABLES_COMPARE_FEEDER a partir del siguiente código:
class ZCL_TABLES_COMPARE_FEEDER definition
public
final
create public .
public section.
interfaces IF_FPM_GUIBB .
interfaces IF_FPM_GUIBB_LIST .
PROTECTED SECTION.
PRIVATE SECTION.
TYPES:
BEGIN OF ty_difference,
tabname TYPE tabname16,
tfname TYPE ztab_compare-tfname,
fdescr TYPE text40,
rkey TYPE ztab_compare-rkey,
v1 TYPE ztab_compare-v1,
v2 TYPE ztab_compare-v2,
END OF ty_difference .
TYPES:
ty_difference_tab TYPE STANDARD TABLE OF ty_difference WITH DEFAULT KEY .
CLASS-DATA gt_data TYPE ty_difference_tab .
ENDCLASS.
CLASS ZCL_TABLES_COMPARE_FEEDER IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~CHECK_CONFIG
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_LAYOUT_CONFIG TYPE REF TO IF_FPM_GUIBB_LIST_CONFIG
* | [<---] ET_MESSAGES TYPE FPMGB_T_MESSAGES
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB_LIST~CHECK_CONFIG.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~FLUSH
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_CHANGE_LOG TYPE FPMGB_T_CHANGELOG
* | [--->] IT_DATA TYPE REF TO DATA
* | [--->] IV_OLD_LEAD_SEL TYPE I(optional)
* | [--->] IV_NEW_LEAD_SEL TYPE I(optional)
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB_LIST~FLUSH.
FIELD-SYMBOLS: <lt_data> LIKE gt_data.
ASSIGN it_data->* TO <lt_data>.
gt_data = <lt_data>.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~GET_DATA
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_EVENTID TYPE REF TO CL_FPM_EVENT
* | [--->] IT_SELECTED_FIELDS TYPE FPMGB_T_SELECTED_FIELDS(optional)
* | [--->] IV_RAISED_BY_OWN_UI TYPE BOOLE_D(optional)
* | [--->] IV_VISIBLE_ROWS TYPE I(optional)
* | [--->] IV_EDIT_MODE TYPE FPM_EDIT_MODE(optional)
* | [--->] IO_EXTENDED_CTRL TYPE REF TO IF_FPM_LIST_ATS_EXT_CTRL(optional)
* | [<---] ET_MESSAGES TYPE FPMGB_T_MESSAGES
* | [<---] EV_DATA_CHANGED TYPE BOOLE_D
* | [<---] EV_FIELD_USAGE_CHANGED TYPE BOOLE_D
* | [<---] EV_ACTION_USAGE_CHANGED TYPE BOOLE_D
* | [<---] EV_SELECTED_LINES_CHANGED TYPE BOOLE_D
* | [<---] EV_DND_ATTR_CHANGED TYPE BOOLE_D
* | [<---] EO_ITAB_CHANGE_LOG TYPE REF TO IF_SALV_ITAB_CHANGE_LOG
* | [<-->] CT_DATA TYPE DATA
* | [<-->] CT_FIELD_USAGE TYPE FPMGB_T_FIELDUSAGE
* | [<-->] CT_ACTION_USAGE TYPE FPMGB_T_ACTIONUSAGE
* | [<-->] CT_SELECTED_LINES TYPE RSTABIXTAB
* | [<-->] CV_LEAD_INDEX TYPE SYTABIX
* | [<-->] CV_FIRST_VISIBLE_ROW TYPE I
* | [<-->] CS_ADDITIONAL_INFO TYPE FPMGB_S_ADDITIONAL_INFO(optional)
* | [<-->] CT_DND_ATTRIBUTES TYPE FPMGB_T_DND_DEFINITION(optional)
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB_LIST~GET_DATA.
DATA: lv_tname TYPE string,
lv_fname TYPE string.
IF iv_eventid->mv_event_id EQ 'FPM_START'.
SELECT tfname, rkey, v1, v2 INTO CORRESPONDING FIELDS OF TABLE @gt_data FROM ztab_compare ORDER BY PRIMARY KEY.
LOOP AT gt_data ASSIGNING FIELD-SYMBOL(<ls_data>).
SPLIT <ls_data>-tfname AT '-' INTO <ls_data>-tabname lv_fname.
IF 'MAKT;MARA;MARC;MARD;MARM;MBEW;MEAN;MLGN;MLGT;MVKE;QMAT' CS <ls_data>-tfname.
IF <ls_data>-v1 EQ abap_true.
<ls_data>-fdescr = | Record exists only in System 1|.
ELSE.
<ls_data>-fdescr = | Record exists only in System 2|.
ENDIF.
ELSE.
<ls_data>-fdescr = |{ lv_fname } : { zcl_tables_compare=>get_table_field_descr( <ls_data>-tfname ) }|.
ENDIF.
ENDLOOP.
ct_data = gt_data.
ev_data_changed = abap_true.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~GET_DEFAULT_CONFIG
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_LAYOUT_CONFIG TYPE REF TO IF_FPM_GUIBB_LIST_CONFIG
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD if_fpm_guibb_list~get_default_config.
DEFINE add_column.
io_layout_config->add_column( iv_name = &2
iv_display_type="TV"
iv_index = &1
iv_header = &3 ).
END-OF-DEFINITION.
io_layout_config->set_settings( iv_height_mode_ats = if_fpm_list_types=>cs_height_mode_ats-all_rows
iv_export_to_excel = abap_true
iv_export_format = if_fpm_list_types=>cs_export_format-office_open_xml
iv_fit_to_table_width = abap_true
iv_selection_mode_ats = if_fpm_list_types=>cs_selection_mode-single_no_lead
iv_scroll_mode = if_fpm_list_types=>cs_scroll_mode-scrolling
iv_allow_sorting = if_fpm_list_types=>cs_settings_allow_sorting-only_ad_hoc
iv_allow_grouping = if_fpm_list_types=>cs_settings_allow_grouping-no_grouping
iv_sort_by_relevance = abap_true
it_default_sorting_ats = VALUE #( ( column_name="TABNAME" is_grouped = abap_true )
( column_name="FDESCR" is_grouped = abap_true ) )
).
TRY.
add_column 1 'TABNAME' 'Table'.
add_column 2 'FDESCR' 'Field'.
add_column 3 'RKEY' 'Key'.
add_column 4 'V1' 'Value in System 1'.
add_column 5 'V2' 'Value in System 2'.
CATCH cx_fpm_configuration. "#EC NO_HANDLER
ENDTRY.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~GET_DEFINITION
* +-------------------------------------------------------------------------------------------------+
* | [<---] EO_FIELD_CATALOG TYPE REF TO CL_ABAP_TABLEDESCR
* | [<---] ET_FIELD_DESCRIPTION TYPE FPMGB_T_LISTFIELD_DESCR
* | [<---] ET_ACTION_DEFINITION TYPE FPMGB_T_ACTIONDEF
* | [<---] ET_SPECIAL_GROUPS TYPE FPMGB_T_SPECIAL_GROUPS
* | [<---] ES_MESSAGE TYPE FPMGB_S_T100_MESSAGE
* | [<---] EV_ADDITIONAL_ERROR_INFO TYPE DOKU_OBJ
* | [<---] ET_DND_DEFINITION TYPE FPMGB_T_DND_DEFINITION
* | [<---] ET_ROW_ACTIONS TYPE FPMGB_T_ROW_ACTION
* | [<---] ES_OPTIONS TYPE FPMGB_S_LIST_OPTIONS
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB_LIST~GET_DEFINITION.
eo_field_catalog ?= cl_abap_tabledescr=>describe_by_data( gt_data ).
et_field_description = VALUE #( ( name="TFNAME" technical_field = abap_true )
( name="TABNAME" allow_sort = abap_true group_same_cells = abap_true )
( name="FDESCR" allow_sort = abap_true group_same_cells = abap_true )
( name="RKEY" )
( name="V1" )
( name="V2" ) ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~PROCESS_EVENT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_EVENT TYPE REF TO CL_FPM_EVENT
* | [--->] IV_RAISED_BY_OWN_UI TYPE BOOLE_D(optional)
* | [--->] IV_LEAD_INDEX TYPE SYTABIX
* | [--->] IV_EVENT_INDEX TYPE SYTABIX
* | [--->] IT_SELECTED_LINES TYPE RSTABIXTAB
* | [--->] IO_UI_INFO TYPE REF TO IF_FPM_LIST_ATS_UI_INFO(optional)
* | [<---] EV_RESULT TYPE FPM_EVENT_RESULT
* | [<---] ET_MESSAGES TYPE FPMGB_T_MESSAGES
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB_LIST~PROCESS_EVENT.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB~GET_PARAMETER_LIST
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RT_PARAMETER_DESCR TYPE FPMGB_T_PARAM_DESCR
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB~GET_PARAMETER_LIST.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB~INITIALIZE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_PARAMETER TYPE FPMGB_T_PARAM_VALUE
* | [--->] IO_APP_PARAMETER TYPE REF TO IF_FPM_PARAMETER(optional)
* | [--->] IV_COMPONENT_NAME TYPE FPM_COMPONENT_NAME(optional)
* | [--->] IS_CONFIG_KEY TYPE WDY_CONFIG_KEY(optional)
* | [--->] IV_INSTANCE_ID TYPE FPM_INSTANCE_ID(optional)
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB~INITIALIZE.
ENDMETHOD.
ENDCLASS.
Luego, la aplicación FPM (algunas pantallas de FPM se verán diferentes en las versiones más nuevas de SAP):
Configurar el diseño de OVP:
agregue allí la lista UIBB con la clase de alimentador creada anteriormente:
Proporcione «ID de configuración» y «Título», guarde y haga clic en «Configurar UIBB» (ignore los errores):
Debido a alguna codificación en la clase de alimentador, no necesita configurar nada más manualmente aquí, excepto una configuración crucial:
Establezca «Contraer grupos de forma predeterminada»; de lo contrario, la aplicación querrá mostrar todos los registros a la vez, lo que podría ser un desafío demasiado grande en el caso de grandes deltas.
(En S/4, la pantalla a continuación se ve diferente, pero aún así la opción se puede encontrar allí)
Después de Guardar, la aplicación debería funcionar, puede iniciarla desde SE80 o con un enlace configurado en SAP GUI:
Cada línea del código proporcionado aquí fue escrita por mí y puedes usarla/modificarla libremente.
Cualquier comentario/pregunta/retroalimentación es bienvenida 🙂
Calle Eloy Gonzalo, 27
Madrid, Madrid.
Código Postal 28010
Paseo de la Reforma 26
Colonia Juárez, Cuauhtémoc
Ciudad de México 06600
Real Cariari
Autopista General Cañas,
San José, SJ 40104
Av. Jorge Basadre 349
San Isidro
Lima, LIM 15073