Archivo Anual: 2009

Charsets y codepages

Introducción

No siempre tenemos que preocuparnos de la codificación de caracteres de los ficheros que abrimos y cerramos en R/3, pero en ocasiones sí. Por ejemplo, cuando destinados a un proyecto en Japón debemos hacer una carga de clientes con nombres etruscos.

Si realmente no estás familiarizado/a con el concepto de juegos de caracteres, es muy importante superar el mito extendidísimo de que un caracter de texto ocupa siempre un byte. Eso es cierto para la mayoría de textos en inglés que no usan caracteres extraños, o cuando utilizamos textos de windows y nuestro SAPGUI está enterado de por donde se mueve. Pero no siempre ocurre así.

Podría extenderme sobre las diversas variaciones de juegos de caracteres, sistemas de codificación, etc… pero no lo voy a hacer porque ya hay un estupendo artículo que explica en que consisten y, esencial, porque llegaron a ser como son: Lo Absolutamente Mínimo que cada Desarrollador de Software, Absoluta y Positivamente, Debe Saber sobre Unicode y Juegos de Caracteres. Este texto, muy clarificador, unido a este FAQ sobre UTF, debería ser bastante para entender lo esencial sobre este asunto.

El código que adjunto es una demostración de cómo se pueden utilizar objetos ABAP para convertir textos de unos sistemas de codificación a en otros. Para facilitar las pruebas, hago uso de la interesante función SCMS_STRING_TO_XSTRING para transformar el texto del parámetro de entrada del report en xstring. ¿Por qué?

Obviamente no podemos trabajar con strings puros, ya que de ese modo su contenido real, sus bytes, permanecen opacos a nuestra vista durante su tratamiento. Por eso se hace absolutamente necesario el uso de xstrings, con lo que a la hora de cargar y grabar ficheros codificados de modo extraño lo haremos en modo binario.

Es importante señalar que ABAP no utiliza los diversos nombres y alias de los distintos juegos de caracteres para realizar las conversiones, ya que utiliza un código numérico interno para definirlos. Existe una función que traduce, más bien intenta, los nombres estándar a estos códigos internos. Pero funciona tan horrorosamente mal y es tan poco práctica que ni la menciono. Es mucho mejor mirar directamente el contenido de la tabla TCP00, cruzar los dedos y deducir, a base de pruebas (este mismo programa puede ayudar) cual es el código numérico que necesitamos. Digo lo de las pruebas porque las descripciones son espantosas y muy poco clarificadoras.

Como curiosidad: el programa RSCPINST muestra los juegos de caracteres utilizados en SAPGUI, servidor y base de datos. Quizás sirva de ayuda.

El código

REPORT  zzcharsetsycodepages NO STANDARD PAGE HEADING.
"As seen on http://cranf.com

DATA windows1252 TYPE xstring. "xcadena en windows1252
DATA utf8 TYPE xstring. "xcadena en UTF-8
DATA utf16 TYPE xstring. "xcadena en UTF-16
DATA conversionutf8 TYPE REF TO cl_abap_conv_x2x_ce. "instancia de la conversión
DATA conversionutf16 TYPE REF TO cl_abap_conv_x2x_ce. "instancia de la conversión
DATA longitud TYPE i. "lo necesitamos, queramos o no

"parámetro de entrada
PARAMETERS cadena TYPE string LOWER CASE DEFAULT 'España Cañí y Olé'.

"**********************************************************************
*     _        _            __           _        _
* ___| |_ _ __(_)_ __   __ _\ \__  _____| |_ _ __(_)_ __   __ _
*/ __| __| '__| | '_ \ / _` |\ \ \/ / __| __| '__| | '_ \ / _` |
*\__ \ |_| |  | | | | | (_| |/ />  <\__ \ |_| |  | | | | | (_| |
*|___/\__|_|  |_|_| |_|\__, |_//_/\_\___/\__|_|  |_|_| |_|\__, |
*                      |___/                              |___/
"en un primer paso tiramos de la función de conversión de string en xstring,
"pasándole el código interno de charset ABAP (están en la tabla TCP00)
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
  EXPORTING
    text     = cadena
    encoding = '1160' "windows1252, véase tabla TCP00 para deducir número
  IMPORTING
    buffer   = windows1252
  EXCEPTIONS
    failed   = 1
    OTHERS   = 2.
"esta función nos puede bastar para conversión de un string en la cadena hexadecimal
"con el charset que nos dé la gana. estupenda, ¿verdad?

"**********************************************************************
*     _                     _                                  _
*  ___ |__  __ _ _ _____ ___ |_   ___ ___ _ ___   ____ _ _____(_)___ _ __
* / __|'_ \/ _` | '__|__| _ \__| / __| _ \ '_ \\ / /_ \ '__|__| | _ \ '_ \
*| (__ | | |(_| | | \__ \ __/|_ | (__ (_) || | |V / __/ | \__ \ |(_) || | |
* \___|| |_|__,_|_| |___/___|__| \___|___/_| |_|_/\___|_| |___/_|___/_| |_|
*
"pero ahora vamos a pasar de un xstring a otro xstring con distinta codificación
"instanciamos primero el objeto conversor
TRY.
    CALL METHOD cl_abap_conv_x2x_ce=>create
      EXPORTING
        in_encoding  = '1160' "windows1252
        ignore_cerr  = abap_false
        out_encoding = '4110' "código utf-8, véase tabla TCP00
        input        = windows1252
      RECEIVING
        conv         = conversionutf8. "es la instancia de un objeto, ojo!
  CATCH cx_parameter_invalid_type .
  CATCH cx_parameter_invalid_range .
  CATCH cx_sy_codepage_converter_init .
ENDTRY.

"convertimos occidental a UTF-8
TRY.
  CALL METHOD conversionutf8->convert_c
    IMPORTING
      len = longitud.
ENDTRY.

"y obtenemos el valor UTF8 como xstring
CALL METHOD conversionutf8->get_out_buffer
  RECEIVING
    buffer = utf8.

"**********************************************************************
"para rizar el rizo, pasamos de UTF8 a UTF16 little endian
TRY.
    CALL METHOD cl_abap_conv_x2x_ce=>create
      EXPORTING
        in_encoding  = '4110' "código utf-8, véase tabla TCP00
        ignore_cerr  = abap_false
        out_encoding = '4103' "utf-16 little endian (4102 sería big endian)
        out_endian   = 'L' "indica opcionalmente big endian / little endian
        input        = utf8
      RECEIVING
        conv         = conversionutf16. "instancia del objeto
  CATCH cx_parameter_invalid_type .
  CATCH cx_parameter_invalid_range .
  CATCH cx_sy_codepage_converter_init .
ENDTRY.

"hacemos la conversión de UTF8 a UTF16
TRY.
  CALL METHOD conversionutf16->convert_c
    IMPORTING
      len = longitud.
ENDTRY.

"obtenemos finalmente el valor UTF16 como xstring
CALL METHOD conversionutf16->get_out_buffer
  RECEIVING
    buffer = utf16.

"**********************************************************************
"y aquí los resultados
WRITE: / 'String      :', cadena.
WRITE: / 'windows1252 :', windows1252.
WRITE: / 'UTF-8       :', utf8.
WRITE: / 'UTF-16      :', utf16.

Tras ejecutarse el programa con el parámetro por defecto, veremos un resultado tal que así:

String      : España Cañí y Olé
windows1252 : 45737061F161204361F1ED2079204F6CE9
UTF-8       : 45737061C3B161204361C3B1C3AD2079204F6CC3A9
UTF-16      : 4500730070006100F1006100200043006100F100ED002000790020004F006C00E900
Publicado en: SAP Sin comentarios ▼

Alta cuentas bancarias cliente/proveedor

Introducción

Este pequeño programa da de alta cuentas bancarias para clientes y proveedores en SAP R/3 de modo rápido y sencillo. Es fácilmente adaptable para las necesidades de cada uno.

Entre otras particularidades, da la opción de generar IBAN automáticamente, asume la existencia de cuentas duplicadas como Warning y no como Error, y la asignación automática de Tipo de Banco.

En caso de tener que borrar cuentas, se debe realizar una rutina muy parecida utilizando el módulo de funciones FIN_AP_AR_DELETE_BANK.

El código

REPORT  zanadircuentasbancarias.

SELECTION-SCREEN BEGIN OF BLOCK b01 WITH FRAME.
PARAMETERS kunnr LIKE kna1-kunnr.
PARAMETERS lifnr LIKE lfa1-lifnr.
SELECTION-SCREEN ULINE.
PARAMETERS banks LIKE lfbk-banks.
PARAMETERS bankl LIKE lfbk-bankl.
PARAMETERS bankn LIKE lfbk-bankn.
PARAMETERS bkont LIKE lfbk-bkont.
PARAMETERS koinh LIKE lfbk-koinh.
SELECTION-SCREEN ULINE.
PARAMETERS makeiban AS CHECKBOX.
PARAMETERS modotest AS CHECKBOX.
SELECTION-SCREEN END OF BLOCK b01.

START-OF-SELECTION.

  DATA retorno TYPE subrc.
  DATA it_ebpp_messages
       TYPE TABLE OF ebpp_messages
       WITH HEADER LINE.

  PERFORM bank_acc_create USING kunnr lifnr
                          banks bankl bankn bkont koinh
                          makeiban
                          modotest
                          CHANGING
                          it_ebpp_messages[]
                          retorno
                        .
  "mostramos mensajes, si los hay (en éxito no hay)
  LOOP AT it_ebpp_messages.
    WRITE: / it_ebpp_messages-msgty,
             it_ebpp_messages-msgid,
             it_ebpp_messages-msgno.
  ENDLOOP.

  "commit or rollback
  IF retorno = 0 AND modotest NE 'X'.
    COMMIT WORK AND WAIT.
  ELSE.
    ROLLBACK WORK.
  ENDIF.

* _               _                                     _
*| |__  __ _ _ __| | __   __ _ ___ ___   ____ _____ __ _ |_ ___
*| '_ \/ _` | '_ \ |/ /  / _` | __| __| / __|'__|_ \ _` |__| _ \
*| |_) |(_| | | | |  <  | (_| |(__ (__ | (__ ||  __/(_| ||_  __/
*|_.__/\__,_|_| |_||\_\  \__,_|___|___| \___|| \___|__,_|__|___|
* as seen in http//glob.cranf.net
* this ascii title created using http://ascii.cranf.net
* compact your batch-input recordings using htpp://bic.cranf.net
FORM bank_acc_create USING
                     pkunnr plifnr "cliente o proveedor
                     pbanks LIKE lfbk-banks "país banco
                     pbankl LIKE lfbk-bankl "código banco
                     pbankn LIKE lfbk-bankn "cuenta
                     pbkont LIKE lfbk-bkont "código control
                     pkoinh LIKE lfbk-koinh "titular
                     piban "crear iban X/space
                     ptest "test X/space
                     CHANGING
                     pitmessages TYPE ebpp_messages_t "mensajes
                     preturn TYPE subrc "retorno tipo subrc
                     .

  "cargamos la estructura lfbk
  DATA lfbk TYPE lfbk.
  CLEAR lfbk.
  lfbk-banks = pbanks.
  lfbk-bankl = pbankl.
  lfbk-bankn = pbankn.
  lfbk-bkont = pbkont.
  lfbk-koinh = pkoinh.

  "dependiendo si cliente o deudor...
  DATA koart TYPE koart.
  IF NOT pkunnr IS INITIAL.
    koart = 'D'.
    lfbk-lifnr = pkunnr.
  ELSE.
    koart = 'K'.
    lfbk-lifnr = plifnr.
  ENDIF.

  " seleccionamos automáticamente un tipo de banco bvtyp
  DATA nextbvtyp LIKE knbk-bvtyp.
  CLEAR nextbvtyp.
  IF koart = 'D'.
    SELECT MAX( bvtyp )
      FROM knbk
      INTO nextbvtyp
      WHERE kunnr = lfbk-lifnr
        AND bvtyp >= '0000'
        AND bvtyp < '9999'.
  ENDIF.
  IF koart = 'K'.
    SELECT MAX( bvtyp )
      FROM lfbk
      INTO nextbvtyp
      WHERE lifnr = lfbk-lifnr
        AND bvtyp >= '0000'
        AND bvtyp < '9999'.
  ENDIF.

  IF nextbvtyp IS INITIAL.
    lfbk-bvtyp = '0001'.
  ELSE.
    lfbk-bvtyp = nextbvtyp + 1.
    CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
      EXPORTING
        input  = lfbk-bvtyp
      IMPORTING
        output = lfbk-bvtyp.
  ENDIF.

  "llamamos a la función de creación
  CALL FUNCTION 'FIN_AP_AR_ADD_BANK'
    EXPORTING
      i_koart      = koart
      i_bankdata   = lfbk
      i_checkmodus = ptest
    TABLES
      t_messages   = pitmessages.

  DATA wamessages LIKE LINE OF pitmessages.

  "prescindimos del error de cuenta duplicada, sólo warning
  LOOP AT pitmessages INTO wamessages.
    IF wamessages-msgid = 'WEBFI_MASTER' AND
 ( wamessages-msgno = '309' "cuenta duplicada, sólo warning
      OR wamessages-msgno = '310' ).
      wamessages-msgty = 'W'.
      MODIFY pitmessages FROM wamessages.
    ENDIF.
  ENDLOOP.

  "decidimos returncode
  preturn = 0. "asumimos fue bien

  LOOP AT pitmessages INTO wamessages WHERE msgty = 'E'.
    preturn = 4. "error al crear
    EXIT.
  ENDLOOP.

  "creación de IBAN
  IF preturn = 0 AND piban = 'X'.

    DATA bnka TYPE bnka.
    CLEAR bnka.

    "lee info del banco
    CALL FUNCTION 'READ_BANK_ADDRESS'
      EXPORTING
        bank_country = lfbk-banks
        bank_number  = lfbk-bankl
      IMPORTING
        bnka_wa      = bnka
      EXCEPTIONS
        not_found    = 1
        OTHERS       = 2.

    IF sy-subrc = 0.

      DATA tiban TYPE tiban.
      CLEAR tiban.
      MOVE-CORRESPONDING lfbk TO tiban.

      CALL FUNCTION 'CONVERT_BANK_ACCOUNT_2_IBAN'
        EXPORTING
          i_bank_account     = tiban-bankn
          i_bank_control_key = tiban-bkont
          i_bank_country     = tiban-banks
          i_bank_number      = bnka-bnklz
          i_bank_key         = bnka-bankl
        IMPORTING
          e_iban             = tiban-iban
        EXCEPTIONS
          no_conversion      = 1
          OTHERS             = 2.

      IF sy-subrc = 0.
        tiban-ernam = sy-uname.
        tiban-erdat = sy-datum - 1. "yesterday...
        IF koart = 'D'.
          tiban-tabname = 'KNBK'.
        ENDIF.
        IF koart = 'K'.
          tiban-tabname = 'LFBK'.
        ENDIF.
        tiban-tabkey = lfbk-lifnr. "mismo para D/K
        MODIFY tiban FROM tiban. "DB!!!
        IF sy-subrc = 0.
          preturn = 0.
        ELSE.
          preturn = 1. "error DB
        ENDIF.
      ELSE.
        preturn = 2. "error al crear IBAN
      ENDIF.
    ELSE.
      preturn = 3. "error al leer banco
    ENDIF.
  ENDIF.

ENDFORM.                    "bank_account_create
Publicado en: SAP Sin comentarios ▼

Macros de alpha y numéricas

Macros Alpha

A la hora de usar la función alpha para meter y quitar ceros siempre confundo las funciones. Además, quedan feo en medio del código. Estas macros facilitan la labor.

*       _       _
*  __ _| |_ __ | |__   __ _
* / _` | | '_ \| '_ \ / _` |
*| (_| | | |_) | | | | (_| |
* \__,_|_| .__/|_| |_|\__,_|
*        |_|
* pone y quita ceros, en plan exit de campo alpha: sólo con variables
*
*  DATA alfa(3) VALUE '3'.
*  vc_ponceros alfa. WRITE alfa.
*  vc_quitaceros alfa. WRITE alfa.

*alpha
***********************************************************************
DEFINE vc_ponceros.
  call function 'CONVERSION_EXIT_ALPHA_INPUT'
    exporting
      input  = &1
    importing
      output = &1.
END-OF-DEFINITION.

DEFINE vc_quitaceros.
  call function 'CONVERSION_EXIT_ALPHA_OUTPUT'
    exporting
      input  = &1
    importing
      output = &1.
END-OF-DEFINITION.

Macros numéricas

En ABAP no hay un método isNaN() como en javascript: ya es hora de que lo haya.

ABAP tampoco tiene Math.random() o equivalente, así que he creado uno por si algún día se necesita.

Por último, a veces el WRITE de ABAP insiste en poner el signo menos del número a la derecha: pues macro para ponerlo a la izquierda.

*                          _      _
* _ __  _   _ _ __ ___   _// _ __(_) ___ ___
*| '_ \| | | | '_ ` _ \ / _ \ '__| |/ __| _ \
*| | | | |_| | | | | | |  __/ |  | | (__ (_) |
*|_| |_|\__,_|_| |_| |_|\___|_|  |_|\___|___/
*
* VC_ISNUMBER cambia sy-subrc en función del parámetro
*  vc_isnumber ' 100'. write sy-subrc. "0 es número
*  vc_isnumber ' 1.4'. write sy-subrc. "1 es número con decimales
*  vc_isnumber 'hola'. write sy-subrc. "2 no es número

* VC_RANDOMIZE devuelve número €[0,1) en la variable float vc_random
*  DO 65000 TIMES.
*    vc_randomize.
*    WRITE:/ vc_random.
*  ENDDO.

* VC_MINUS2LEFT pone el signo menos de un número negativo a la izq.
*  después de realizar un WRITE TO de integer a char
*  justifica valores a la derecha, eliminando espacio al final
*  DATA n TYPE i VALUE '-123'. DATA t(10).
*  WRITE n TO t.
*  WRITE / t. vc_minus2left t. WRITE / t. "123- >>> -123

*numérico
***********************************************************************
DEFINE vc_isnumber.
  if &1 co ' 0123456789'.
    sy-subrc = 0.
  else.
    if &1 co ' 0123456789.,'.
      sy-subrc = 1.
    else.
      sy-subrc = 2.
    endif.
  endif.
END-OF-DEFINITION.

DATA vc_random TYPE float.

DEFINE vc_randomize.
  vc_random = vc_random +
    ( sy-uzeit + sy-datum + sy-tabix + sy-index ) / 39916801. "primo
  vc_random = abs( vc_random ) mod 1.
  vc_random = ( 100 * log( vc_random ) ) mod 1.
END-OF-DEFINITION.

DEFINE vc_minus2left.
  search &1 for '-'.
  if sy-subrc = 0.
    translate &1 using '- '.
    concatenate '-' &1 into &1.
    condense &1 no-gaps.
  endif.
  write &1 to &1 right-justified.
END-OF-DEFINITION.

Shameless self-promotion

Las letras grandotas están hechas con http://ascii.cranf.net

Publicado en: SAP Sin comentarios ▼
[c] Alberto Viñuela Miranda / Cranfcom 2013-2014

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.