En entornos productivos donde se gestionan volúmenes considerables de materiales, los departamentos de calidad o logística suelen solicitar reportes que combinan datos maestros con atributos clasificatorios específicos: por ejemplo, el lote de proveedor, la certificación ambiental o la vida útil de un conjunto de productos. La consulta individual a través de transacciones estándar como CL20N o MM03 resulta inviable cuando se trata de cientos o miles de materiales, además de que los nombres de las características pueden variar entre clases de clasificación.
El sistema de clasificación de SAP almacena estos atributos como pares clave-valor en la tabla AUSP, vinculados a los objetos mediante INOB, y con sus metadatos en CABN y CABNT. Aunque potente, esta arquitectura no está pensada para consultas transversales masivas. Por ello, desarrollar un programa ABAP personalizado que extraiga, transforme y presente estos datos de forma dinámica se convierte en una solución de gran valor.
Arquitectura de la solución: del modelo EAV a una estructura tabular
Los datos de características en SAP siguen un patrón similar a Entity-Attribute-Value (EAV): cada registro en AUSP representa una combinación objeto-característica-valor. El desafío consiste en pivotar esa estructura vertical a una representación horizontal donde cada característica se convierta en una columna.
El procesamiento se divide en tres fases:
- Extracción masiva: recuperar todos los pares característica-valor asociados a los materiales seleccionados, consultando
INOB,AUSPyCABN. - Enriquecimiento: vincular con datos maestros de
MARAyMAKTpara incorporar descripciones, unidades y dimensiones. - Construcción dinámica del ALV: determinar en tiempo de ejecución qué características están presentes en el resultado y generar una tabla interna con estructura variable, utilizando
cl_alv_table_create=>create_dynamic_tableo la API de RTTS.
La técnica de tabla interna dinámica es el núcleo de la solución, ya que permite que el reporte se adapte automáticamente a las características encontradas en cada ejecución.
Definición de tipos y estructuras de datos
Comenzamos declarando los tipos que utilizaremos como contenedores intermedios durante el procesamiento. Se han renombrado y reorganizado respecto a un enfoque convencional para mejorar la legibilidad:
*&---------------------------------------------------------------------*
*& Declaración de TYPES
*&---------------------------------------------------------------------*
TYPES: BEGIN OF t_matkey,
matnr TYPE mara-matnr,
END OF t_matkey.
TYPES: BEGIN OF t_chardef,
internal_id TYPE cabn-atinn,
tech_name TYPE cabn-atnam,
label_text TYPE cabnt-atbez,
END OF t_chardef.
TYPES: BEGIN OF t_rawval,
obj_key TYPE ausp-objek,
char_id TYPE cabn-atinn,
char_name TYPE cabn-atnam,
char_text TYPE ausp-atwrt,
char_num TYPE ausp-atflv,
END OF t_rawval.
TYPES: BEGIN OF t_enriched_val,
obj_key TYPE ausp-objek,
char_name TYPE cabn-atnam,
char_text TYPE ausp-atwrt,
size_text TYPE mara-groes,
norm_code TYPE mara-normt,
mat_desc TYPE makt-maktx,
END OF t_enriched_val.
TYPES: BEGIN OF t_codedesc_ref,
char_id TYPE cawnt-atinn,
code_grp TYPE cawnt-atwtb,
END OF t_codedesc_ref.
Captura de datos: selección desde tablas de clasificación
La primera etapa del bloque de procesamiento consiste en leer los materiales dentro del rango ingresado y luego recuperar todos los valores de características asociados. Se utiliza INOB como puente entre el número de material y el identificador interno de objeto que utiliza AUSP.
*&---------------------------------------------------------------------*
*& Recuperación de datos de clasificación
*&---------------------------------------------------------------------*
DATA: r_matnr TYPE RANGE OF mara-matnr,
lt_rawval TYPE TABLE OF t_rawval,
ls_rawval TYPE t_rawval.
SELECT objek, atinn, atwrt, atflv
FROM ausp
INTO TABLE @DATA(lt_ausp_raw)
FOR ALL ENTRIES IN @lt_materials
WHERE objek = @lt_materials-objek
AND klart = '001'.
IF sy-subrc = 0.
SELECT atinn, atnam
FROM cabn
INTO TABLE @DATA(lt_cabn_map)
FOR ALL ENTRIES IN @lt_ausp_raw
WHERE atinn = @lt_ausp_raw-atinn.
LOOP AT lt_ausp_raw INTO DATA(ls_ausp).
ls_rawval-obj_key = ls_ausp-objek.
ls_rawval-char_id = ls_ausp-atinn.
ls_rawval-char_text = ls_ausp-atwrt.
ls_rawval-char_num = ls_ausp-atflv.
READ TABLE lt_cabn_map INTO DATA(ls_cabn)
WITH KEY atinn = ls_ausp-atinn.
IF sy-subrc = 0.
ls_rawval-char_name = ls_cabn-atnam.
ENDIF.
APPEND ls_rawval TO lt_rawval.
ENDLOOP.
ENDIF.
Enriquecimiento con datos maestros
Una vez disponibles los valores crudos, se incorporan los textos descriptivos del material y atributos relevantes de MARA. Esto permite que el reporte final incluya no solo las características clasificatorias sino también información de contexto:
*&---------------------------------------------------------------------*
*& Unión con datos maestros de material
*&---------------------------------------------------------------------*
DATA: lt_enriched TYPE TABLE OF t_enriched_val,
ls_enriched TYPE t_enriched_val.
SELECT m~matnr, m~groes, m~normt, t~maktx
FROM mara AS m
INNER JOIN makt AS t
ON m~matnr = t~matnr
FOR ALL ENTRIES IN @lt_rawval
WHERE m~matnr = @lt_rawval-obj_key
AND t~spras = @sy-langu
INTO TABLE @DATA(lt_mara_join).
LOOP AT lt_rawval INTO ls_rawval.
ls_enriched-obj_key = ls_rawval-obj_key.
ls_enriched-char_name = ls_rawval-char_name.
ls_enriched-char_text = ls_rawval-char_text.
READ TABLE lt_mara_join INTO DATA(ls_mara)
WITH KEY matnr = ls_rawval-obj_key.
IF sy-subrc = 0.
ls_enriched-size_text = ls_mara-groes.
ls_enriched-norm_code = ls_mara-normt.
ls_enriched-mat_desc = ls_mara-maktx.
ENDIF.
APPEND ls_enriched TO lt_enriched.
ENDLOOP.
Construcción de la tabla dinámica con RTTS
Para generar una tabla interna cuya estructura depende de las características encontradas en teimpo de ejecución, empleamos el servicio de tipos de ejecución de ABAP (Run Time Type Services). El proceso consiste en:
- Extraer la lista única de características presentes en
lt_enriched. - Construir un descriptor de tabla (
cl_abap_tabledescr) a partir de componentes de tipocl_abap_elemdescr. - Crear la referencia de datos y asignarla a un field-symbol.
- Popular cada fila recorriendo los materiales y buscando sus valores por característica.
*&---------------------------------------------------------------------*
*& Generación dinámica de estructura de tabla
*&---------------------------------------------------------------------*
DATA: lt_components TYPE cl_abap_structdescr=>component_table,
ls_component LIKE LINE OF lt_components.
* Componente fijo: número de material
ls_component-name = 'MATNR'.
ls_component-type = cl_abap_elemdescr=>get_c( 18 ).
APPEND ls_component TO lt_components.
* Componente fijo: descripción
ls_component-name = 'MAKTX'.
ls_component-type = cl_abap_elemdescr=>get_c( 40 ).
APPEND ls_component TO lt_components.
* Componentes dinámicos: una columna por característica encontrada
DATA(lt_distinct_chars) = lt_enriched.
SORT lt_distinct_chars BY char_name.
DELETE ADJACENT DUPLICATES FROM lt_distinct_chars COMPARING char_name.
LOOP AT lt_distinct_chars INTO DATA(ls_distinct).
ls_component-name = ls_distinct-char_name.
ls_component-type = cl_abap_elemdescr=>get_c( 50 ).
APPEND ls_component TO lt_components.
ENDLOOP.
* Crear descriptor y tabla dinámica
DATA(lo_structdescr) = cl_abap_structdescr=>create( lt_components ).
DATA(lo_tabledescr) = cl_abap_tabledescr=>create( lo_structdescr ).
DATA: dref_table TYPE REF TO data,
dref_row TYPE REF TO data.
CREATE DATA dref_table TYPE HANDLE lo_tabledescr.
CREATE DATA dref_row TYPE HANDLE lo_structdescr.
FIELD-SYMBOLS: <ft_dyntab> TYPE STANDARD TABLE,
<fs_dynrow> TYPE any.
ASSIGN dref_table->* TO <ft_dyntab>.
ASSIGN dref_row->* TO <fs_dynrow>.
Poblado de la tabla dinámica
Con la tabla dinámica disponible, iteramos sobre los materiales únicos y completamos cada columna buscando el valor correspondiente en lt_enriched:
*&---------------------------------------------------------------------*
*& Llenado de tabla dinámica
*&---------------------------------------------------------------------*
DATA(lt_unique_mats) = lt_enriched.
SORT lt_unique_mats BY obj_key.
DELETE ADJACENT DUPLICATES FROM lt_unique_mats COMPARING obj_key.
LOOP AT lt_unique_mats INTO DATA(ls_mat).
CLEAR <fs_dynrow>.
ASSIGN COMPONENT 'MATNR' OF STRUCTURE <fs_dynrow> TO FIELD-SYMBOL(<fv_matnr>).
<fv_matnr> = ls_mat-obj_key.
ASSIGN COMPONENT 'MAKTX' OF STRUCTURE <fs_dynrow> TO FIELD-SYMBOL(<fv_maktx>).
<fv_maktx> = ls_mat-mat_desc.
LOOP AT lt_enriched INTO DATA(ls_val)
WHERE obj_key = ls_mat-obj_key.
ASSIGN COMPONENT ls_val-char_name OF STRUCTURE <fs_dynrow>
TO FIELD-SYMBOL(<fv_char>).
IF sy-subrc = 0.
<fv_char> = ls_val-char_text.
ENDIF.
ENDLOOP.
APPEND <fs_dynrow> TO <ft_dyntab>.
ENDLOOP.
Visualización mediante ALV dinámico
Finalmente, se presenta el resultado utilizando cl_salv_table, que maneja de forma transparente estructuras dinámicas:
*&---------------------------------------------------------------------*
*& Visualización ALV
*&---------------------------------------------------------------------*
TRY.
cl_salv_table=>factory(
IMPORTING r_salv_table = DATA(lo_alv)
CHANGING t_table = <ft_dyntab> ).
lo_alv->display( ).
CATCH cx_salv_msg INTO DATA(lx_msg).
MESSAGE lx_msg TYPE 'E'.
ENDTRY.