Un poco de matemáticas básicas con el SEPA

Para cumplir con la legislación de pago electrónico, apareció un Hotfix que incorporaba la funcionalidad del SEPA en distintas versiones de AX. Bien, según esta nueva funcionalidad, el sistema es capaz de generar un fichero XML con la información de los cobros y los pagos. Este fichero se envía a la entidad bancaria correspondiente y esta se encarga de gestionar dichas operaciones. Hasta aquí todo correcto, pero se da la circunstancia de que, en determinados casos, esta generación del fichero XML provoca un error de desbordamiento de Int64.

¿Y ahora qué?

Bien, investigando un poco, descubrimos que el problema se produce al aplicar una transformación (XSLT) del XML mediante un fichero XSL que se encuentra entre los recursos del AOT (CustPayments_SEPADirectDebit…. en la rama Resources). En concreto, en una línea de código existente en este XSL que realiza la siguiente operación:

checkDigit = 98 - Convert.ToInt64(tmpCredRef.ToString()) % 97;

Se trata tan solo de un cálculo de un dígito de control, a continuación tenéis la función completa:

public string CalculateISOCredRef(string refStr)
{
    StringBuilder tmpCredRef = new StringBuilder();
    string tmpStr = refStr;
    Int64 checkDigit = 0;

    tmpStr += "RF00";

    tmpStr = tmpStr.ToUpperInvariant();

    foreach (char c in tmpStr.ToCharArray())
    {
        if (Char.IsLetter(c))
        {
            tmpCredRef.Append(((int)c - 55).ToString());
        }
        else
        {
            tmpCredRef.Append(c.ToString());
        }
    }

    if (!String.IsNullOrEmpty(tmpCredRef.ToString()))
    {
        checkDigit = 98 - Convert.ToInt64(tmpCredRef.ToString()) % 97;
    }
   
    if (checkDigit < 10)
    {
        refStr = "RF0" + checkDigit.ToString() + refStr;
    }
    else
    {
        refStr = "RF" + checkDigit.ToString() + refStr;
    }

    return refStr;
}

¿Cual es el problema?

 

El problema radica cuando la cadena de texto que está convirtiendo a numérica es demasiado larga.

Es decir, cuando tmpCredRef tiene demasiados dígitos, la conversión a numérico de 64bits provoca un desbordamiento de tipo.

 

Resulta que está realizando esta conversión para calcular el módulo 97 de dicho número y restarlo a 98, lo que provocará un resultado que será el dígito de control.

 

¿Cual es la solución?

 

Tenemos una cadena de texto que contiene un número demasiado largo para que podamos convertirlo a numérico y poder hacer cálculos con el.

 

Bien, si afrontamos el problema desde un punto de vista puramente matemático, podemos comprobar que:

 

Si, por ejemplo, calculamos para el número 12745:

 

12745 mod 97 = 38

 

Y si por otro lado tratamos las cifras de este número por separado:

 

Separamos 12745 llevándonos los tres primeros dígitos (127)

[A] 127 mod 97 = 30

 

[B] 45 mod 97 = 45

 

Ahora aplicamos a [A] las decenas que hemos “recortado” anteriormente:

 

30*100 = 3000

 

Y lo sumamos a [B]

 

[C] 3000 + 45 = 3045

 

Si ahora calculamos el módulo 97 de [C] tenemos que:

 

3045 mod 97 = 38

 

Nos da el mismo resultado que cuando hemos calculado el modulo 97 de 12745 y, en cambio, trabajamos con menos cifras…

 

Eso nos permite pensar en un número aproximado de cifras que podemos recortar del número largo que nos provocaba error para poder tratar dos números de, como mucho este número de cifras y que el resultado no se vea alterado (15 dígitos es una cifra “aceptable”)

 

Por tanto, modificamos el código de la siguiente forma:

 

public string CalculateISOCredRef(string refStr)
{
    StringBuilder tmpCredRef = new StringBuilder();
    string tmpStr = refStr;
    string tmpStr1;
    string tmpStr2;
    Int64 checkDigit  = 0;
    Int64 checkDigit1 = 0;
    Int64 checkDigit2 = 0;
    int len;
    int factor;


    tmpStr += "RF00";

    tmpStr = tmpStr.ToUpperInvariant();
 
 
    foreach (char c in tmpStr.ToCharArray())
    {
        if (Char.IsLetter(c))
        {
            tmpCredRef.Append(((int)c - 55).ToString());
        }
        else
        {
          tmpCredRef.Append(c.ToString());
        }
    }
 
    if (!String.IsNullOrEmpty(tmpCredRef.ToString()))
    {
        tmpStr  = tmpCredRef.ToString();
        len     = tmpStr.Length;
        if (len > 15)
        {
            factor      = (int)Math.Pow(10, (len - 15));
            tmpStr1     = tmpStr.Substring(0 ,15);
            checkDigit1 = (Convert.ToInt64(tmpStr1) % 97) * factor;
           
            tmpStr2     = tmpStr.Substring(15);
            checkDigit2 = Convert.ToInt64(tmpStr2) % 97;
           
            checkDigit  = checkDigit1 + checkDigit2;
            checkDigit  = 98 - checkDigit % 97;
        }
        else
          checkDigit = 98 - Convert.ToInt64(tmpStr) % 97;
    }
   
    if (checkDigit < 10)
    {
        refStr = "RF0" + checkDigit.ToString() + refStr;
    }
    else
    {
        refStr = "RF" + checkDigit.ToString() + refStr;
    }
 
    return refStr;
}

Con esto solucionamos el problema para casos no mayores a 30 dígitos (ya nos soluciona todas las casuísticas que se daban en nuestro caso concreto del SEPA).

Saludos.

Publicaciones Recientes

Dejar comentario