HOWTO: Facturación selectiva de líneas en Dynamics AX

En Microsoft Dynamics AX, como en la gran mayoría de ERP’s, se pueden facturar líneas de forma parcial y agruparlas según diferentes criterios. El usuario puede elegir el número de líneas que desee:

PurchFormLetter | 1

y facturarlas completamente o en parte, dejando el resto pendiente de facturar o no, agrupandolas en facturas por clientes, por dirección, etc.:

PurchFormLetter | 2

Incluso para cada línea se pueden cambiar en ese momento las condiciones a facturar:

PurchFormLetter | 3

Todo esto es facil e intuitivo desde los formularios propios de Microsoft Dynamics AX, pero se vuelve mas complicado a la hora de simularlo desde código X++. Facturar pedidos completos es muy facil pero según se quiere granular mas la facturación y tener mas control o modificar detalles concretos de cada línea el código resulta mas complicado.

Este es un ejemplo que puede servir como base de código para facturar líneas una a una, indicar el número y fecha de la factura que las englobará todas y modificar gran parte de las opciones que son personalizables (básicamente, las que se muestran en el formulario capturado mas arriba):

static void JAEE_PurchFormLetter(Args _args)
{
    Num                 _invoiceNum;    // Núm. factura
    TransDate           _invoiceDate;   // Fecha factura

    PurchFormLetter     purchFormLetter = PurchFormLetter::construct(DocumentStatus::Invoice);
    PurchParmUpdate     purchParmUpdate;
    PurchParmTable      purchParmTable;
    PurchParmLine       purchParmLine;

    TradeLineRefId      tableRefId;
    PurchTable          purchTable;
    PurchLine           purchLine;
    ;

    ttsbegin;

    purchTable = PurchTable::find('PO-100104'); // Primer pedido

    // Inizializar purchFormLetter con el primer pedido
    purchFormLetter.parmCallerTable(purchTable);
    purchFormLetter.callInitParmPurchTable(purchTable);
    purchFormLetter.proforma(false);                                // Proforma: NO (Registrar: SI)
    purchFormLetter.enableUpdateNowField(true);                     // Actualizar ahora: SI
    purchFormLetter.printFormLetter(false);                         // Imprimir: NO
    purchFormLetter.transDate(_invoiceDate);                        // Fecha de factura

    // Inizializar purchParmUpdate con el primer pedido
    purchParmUpdate.clear();
    purchParmUpdate.initValue();
    purchParmUpdate.ParmId              = purchFormLetter.parmId();
    purchParmUpdate.SumBy               = AccountOrder::Account;    // Agrupar por cliente
    purchParmUpdate.SumNum              = _invoiceNum;              // Núm. Factura
    purchParmUpdate.SpecQty             = PurchUpdate::All;         // Actualizar: Todo
    purchParmUpdate.DocumentStatus      = DocumentStatus::Invoice;  // Tipo documento: Factura
    purchParmUpdate.Proforma            = NoYes::No;                // Proforma: NO
    purchParmUpdate.SumIncludePending   = NoYes::No;                // Incluir pendiente: NO
    if (purchParmUpdate.validateWrite())
        purchParmUpdate.insert();
    else
        throw Exception::Error;

    purchFormLetter.purchParmUpdate(purchParmUpdate);
    purchFormLetter.parmParmTableNum(purchParmUpdate.SumNum);

    // Tabla temporal, se crea un registro de cabecera del primer pedido (sólo uno)
    purchParmTable.clear();
    purchParmTable.TableRefId = FormLetter::getTableRef();
    purchFormLetter.createParmTable(purchParmTable, purchTable);
    if (purchParmTable.validateWrite())
        purchParmTable.insert();
    else
        throw Exception::Error;

    tableRefId  = purchParmTable.TableRefId;

    // BEGIN - LINEAS
    //       - Repetir para cada línea que se quiera facturar y para cada una y
    //       - asignar las variabies purchTable y purchLine según proceda
    purchParmLine.clear();
    purchParmLine.initValue();

    // Ajustar cantidades según necesidades
   
    // Catnidades de compra
    [purchParmLine.ReceiveNow,
     purchParmLine.RemainBefore,
     purchParmLine.RemainAfter] = purchFormLetter.qtyPurch(purchLine, naReal());

    // Cantidades de inventario
    [purchParmLine.InventNow,
     purchParmLine.RemainBeforeInvent,
     purchParmLine.RemainAfterInvent] = purchFormLetter.qtyInvent(purchLine, naReal());

    if (purchParmLine.ReceiveNow)
    {
        purchParmLine.ParmId = purchParmUpdate.ParmId;
        purchParmLine.initFromPurchLine(purchLine);
        purchParmLine.setLineAmount();
        purchParmLine.TableRefId = tableRefId;

        if (purchParmLine.validateWrite())
            purchParmLine.insert();
        else
            throw Exception::Error;
    }
    // END - LINEAS

    // Registrar!
    purchFormLetter.reArrangeNow(false); // No organizar
    purchFormLetter.run();

    ttscommit;
}

Este código hace usa de las clase PurchFormLetter para compras, pero también es adaptable a las clases SalesFormLetter para facturación de ventas.

Descarga

6 comentarios
  • Sergio Sepúlveda Montealegre
    marzo 15, 2013

    Hola jaestevan,

    Primero que todo, felicitaciones por el blog, está excelente.
    Ya has trabajado en este código para AX 2012?

    Saludos,

    Sergio

  • Axel
    enero 16, 2013

    Hola jaestevan,

    La versión es 2009.

    Hice una clase derivada de PurchFormLetter y ahí implementé el método “callInitParmPurchTable”, en tu código declaré purchFormLetter como de mi clase “MyPurchFormLetter”. Tuve algunos inconvenientes, pero creo que ya los resolví. Si te interesa (o a alguien más) aquí hay un link de StackOverflow donde se resuelve : http://stackoverflow.com/questions/14323743/programmatically-add-an-invoice-to-dynamics-ax-2009/14363148#14363148

    Gracias por tu respuesta, seguiré trabajando con esto.

  • jaestevan
    enero 16, 2013

    Hola Axel

    ¿Estás trabajando con AX versión 2012? El código funcionaba correctamente cuando lo publiqué,, pero es código para la versión 2009 y para utilizarlo en una versión distinta puede ser necesaria alguna modificación, aunque la base no ha cambiado mucho.

    El error del método protegido se puede evitar simplemente cambiando private por public en el método. Esto no afectará a la funcionalidad y permitirá llamar al método desde otras clases, si fuera necesario.

    En cuanto a heredar esta clase, es una buena idea y un procedimiento recomendado para ampliar el funcionamiento de clases estándar.

    Saludos.

  • Axel
    enero 11, 2013

    Bueno, lo que he hecho hasta ahora es una clase que hereda de PurchFormLetter y ahí es donde implementé el método “callInitParmPurchTable”.

    Seguiré trabajando con esto,

    Gracias de nuevo!

  • Axel
    enero 11, 2013

    Hola j.a.estevan,

    Antes que nada muchas gracias por este post. Brinda un buen punto de partida para el requerimiento que estoy tratando de cumplir.

    La verdad es que voy empezando con Dynamics AX y no estoy muy familiarizado con la tecnología, pero te quería hacer un comentario y pregunta a la vez.

    He colocado el código que has publicado en un job y me encontré con que el compilador me manda un mensaje de que el método callInitParmPurchTable no existe

    purchFormLetter.callInitParmPurchTable(purchTable);

    Así que investigué la clase y veo que hay un método que se llama “initParmPurchTable”, pero es protegido y ahora el compilador muestra el siguiente mensaje:

    “El método se ha declarado protegido y sólo se puede llamar desde los métodos en las clases derivadas de PurchFormLetter.

    ¿Podrías orientarme un poco al respecto?

    Muchas gracias de antemano.

  • JosepMP
    febrero 16, 2012

    Buenas…

    Estoy intentando crear un Albarán usando la clase SalesFormLetter (albarán de ventas)
    De echo, ya he conseguido lanzar un albarán a través de código teniendo en cuenta una PickingList.
    Puedo incluso lanzar 2 albaranes a partir de 2 pedidos, cuyas PikingList acaban de ser actualizadas (con salesFormLetter.chooseLinesQuery(queryRun))
    Me gustaría poder agrupar los 2 albaranes (o más) en uno (OrderAccount::Acount), pero el proceso nunca me lanza un único albarán, siempre me lanza uno para cada SalesId.

    El proceso que tengo entre manos primero actualiza las PicikingList de cada pedido de ventas y después, conociendo las SalesId que han sido entragadas, intento confeccionar un albarán conjunto (para el mismo cliente, se entiende)

    salesFormLetter.sumBy(sumByMethod);
    salesFormLetter.chooseLinesQuery(queryRun);
    salesFormLetter.transDate(systemdateget());
    salesFormLetter.specQty(SalesUpdate::PickingList);
    salesFormLetter.printFormLetter(true);
    salesFormLetter.createParmUpdate();
    salesFormLetter.chooseLines(null,true);
    salesFormLetter.reArrangeNow(true);
    salesFormLetter.run();

    // Atención a la sentencia salesFormLetter.chooseLinesQuery(queryRun);
    queryRun se basa en el query SalesUpdate y recoge convenientemente las SalesId que necesito meter en el albarán.
    Como comentaba, asles los albaranes, pero no agrupados.

    Cualquier ayuda será bienvenida… y si puedes contactar por eMAIL, mejor.

    Josep