martes, 21 de febrero de 2012

Transaction Text

The Base > Config > "Transaction Texts" has a desceptively simple funcionality on the face of it yet provides us with a very useful tool. All of our ledgers can now be filled with wonderful texts that include the list I've copy/pasted from Microsoft below:
  • The Date field. To enter the date in this field, type %1.
  • The ID of program used to generate the information field. To enter this information, type %2.
  • The Voucher number field. To enter the voucher number or the vendor invoice number in this field, type %3.
  • Parameters %4 through %6 are reserved for user adjustments. These parameters can be used only by the system administrator.
That is to say you are just going to have to suck it and see for the %4 to %6 values.

However, when the client asks you to put the name of the client/supplier in the %4 field (warning: the transaction 'txt' is of somewhat limited space) they look at you incredulously as you can't give them a quick estimation as to how long it'll take. The problem being that yes, the TransactTxt table and the corresponding TransactionTxt class are very simple but we don't know where they are used. The key, so to speak, is with the LedgerTransTxt enumeration. Find where it is used in the classes and we are good.

My conclusion is that the changes themselves aren't difficult to undertake. It's just finding which classes to change and then applying rigorous testing post development to ensure that we aren't modifying other ledger texts other than what we desire.



The last part of this post is probably uninteresting to you, but these are notes to myself now ;)

The image below are what the analyst selected to add our client/supplier name functionality using the '%4' tag.  I've highlighted the registering of sales service and sales order invoices as an example.  This is a non exhaustive list and there are many other transaction types we can hook our free text on.

And below are indicators as to the changes to the classes I made:

Clientes -> Registrar factura del Pedido de venta

 * LedgerTransTxt::SalesInvoiceLedger : @SYS13480 : Pedido de ventas - Factura, Contabilidad
 - Asientos (LedgerTrans)
 - SalesFormLetter_Invoice.initTransactionTxt(...)

 * LedgerTransTxt::SalesInvoiceCust   : @SYS9935  : Pedido de ventas - Factura, Cliente
 - Cliente transacciones (CustTrans), Asientos (LedgerTrans)
 - CustVendVoucher.setTransactionTxt(...)

Clientes -> Factura de Servicios -> Registrar: 

 * LedgerTransTxt::CustInvoiceLedger  : @SYS57440 : Cliente - Factura, Contabilidad
 - Asientos (LedgerTrans)
 - CustPostInvoice.run(...) -> 169

 * LedgerTransTxt::CustInvoiceCust    : @SYS57441 : Cliente - Factura, Cliente
 - Cliente transacciones (CustTrans), Asientos (LedgerTrans)
 - CustVendVoucher.setTransactionTxt(...)

Proveedores -> Registrar factura del pedido de compra

 * LedgerTransTxt::PurchInvoiceLedger : @SYS2653  : Pedido de compra - Factura, Contabilidad
 - Asientos (LedgerTrans)
 - PurchFormLetter_Invoice.initTransactionTxt(...)

 * LedgerTransTxt::PurchInvoiceVend   : @SYS14768 : Pedido de compra - Factura, Proveedor
 - Proveedor transacciones (VendTrans), Asientos (LedgerTrans)
 - CustVendVoucher.setTransactionTxt(...)

Clientes -> Registrar factura del Pedido de venta con una cantidad negativa

 * LedgerTransTxt::SalesCreditNoteLedger : @SYS6176 : Pedido de ventas - Nota de abono, Contabilidad
 - Asientos (LedgerTrans)
 - SalesFormLetter_Invoice.initTransactionTxt(...)

 * LedgerTransTxt::SalesCreditNoteCust : @SYS9529 : Pedido de ventas - Nota de abono, Cliente
 - Cliente transacciones (CustTrans), Asientos (LedgerTrans)
 - CustVendVoucher.setTransactionTxt(...)

Proveedores -> Registrar factura del Pedido de compra con una cantidad negativa

 * LedgerTransTxt::PurchCreditNoteLedger : @SYS11877 : Pedido de compra - Nota de abono, Contabilidad
 - Asientos (LedgerTrans)
 - PurchFormLetter_Invoice.initTransactionTxt(...)

 * LedgerTransTxt::PurchCreditNoteVend   : @SYS13408 : Pedido de compra - Nota de abono, Proveedor
 - Proveedor transacciones (VendTrans), Asientos (LedgerTrans)
 - CustVendVoucher.setTransactionTxt(...)

Clientes -> Factura de servicios -> Registrar -VE cantidad 

 * LedgerTransTxt::CustCreditNoteLedger : @SYS57442 : Cliente - Nota de abono, Contabilidad  
 - Asientos (LedgerTrans)
 - CustPostInvoice.run(...) -> 169

 * LedgerTransTxt::CustCreditNoteCust   : @SYS57443 : Cliente - Nota de abono, Cliente
 - Cliente transacciones (CustTrans), Asientos (LedgerTrans)
 - CustVendVoucher.setTransactionTxt(...)

Pendiente!

 * LedgerTransTxt::VendCashPayment      : @SYS28685 : Proveedor - Pago en efectivo
 - (VendTransData.cashledgerTransTxt())
 * LedgerTransTxt::CustCashPayment      : @SYS28684 : Cliente - Pago en efectivo
 - (CustTransData.cashledgerTransTxt())

Proveedores -> Diario de Pagos/Cobros -> Propuesta de pago
Proveedores -> Pagares -> Diario de creación de pagares -> Propuesta de pago
Proveedores -> Pagares -> Diario de envíos -> Propuesta de pago
Proveedores -> Pagares -> Diario de liquidación de pagos -> Propuesta de pago
Proveedores -> Diario de Pagos/Cobros -> Liquidación (LedgerJournalTrans.PaymentNotes)
Proveedores -> Pagares -> Diario de creación de pagares -> Liquidación (LedgerJournalTrans.PaymentNotes)


 * LedgerTransTxt::VendPaymentVend    : @SYS1390  : Proveedor - Pago, Proveedor
 - Diario de pagos (LedgerJournalTrans), Proveedor transacciones (VendTrans), Asientos (LedgerTrans)
 - CustVendPaymProposalTransferToJournal.getTransactionText() - Texto de la transacción   (LedgerJournalTrans.Txt)
 - CustVendPaymNote.buildPaymNote() -> 74                     - Nota asociado con el pago (LedgerJournalTrans.PaymentNotes)

Clientes -> Diario de Pagos/Cobros -> Propuesta de pago
Clientes -> Letra de cambio -> Diario de creación de letra de cambio -> Propuesta de pago
Clientes -> Letra de cambio -> Diario de envíos -> Propuesta de pago
Clientes -> Letra de cambio -> Diario de liquidación... -> Propuesta de pago
Clientes -> Diario de Pagos/Cobros -> Liquidación (LedgerJournalTrans.PaymentNotes)
Clientes -> Letra de cambio -> Diario de envíos -> Liquidación (LedgerJournalTrans.PaymentNotes)

 * LedgerTransTxt::CustPaymentCust    : @SYS60760 : Cliente - Pago, Cliente
 - Diario de pagos (LedgerJournalTrans), Cliente transacciones (CustTrans), Asientos (LedgerTrans)
 - CustVendPaymProposalTransferToJournal.getTransactionText() - Texto de la transacción   (LedgerJournalTrans.Txt)
 - CustVendPaymNote.buildPaymNote() -> 74                     - Nota asociado con el pago (LedgerJournalTrans.PaymentNotes)

-------------------
¿¿¿ Not found or used ???
 * LedgerTransTxt::CustBillOfExchangeLedger : @SYS71601 : Cliente - Letra de cambio, contabilidad
 - NOT USED?

 * LedgerTransTxt::CustBillOfExchangeCust   : @SYS71602 : Cliente - Letra de cambio, cliente
 - NOT USED?

 * LedgerTransTxt::VendPaymentLedger        : @SYS14411 : Proveedor - Pago, Contabilidad
 - NOT USED?

 * LedgerTransTxt::VendPromissoryNoteVend   : @SYS71605 : Proveedor - Pagaré, Proveedor
 - NOT USED?

 * LedgerTransTxt::VendPromissoryNoteLedger : @SYS71604 : Proveedor - Pagaré, Contabilidad
 - NOT USED?


viernes, 10 de febrero de 2012

My first CrossCompany update

Objective: From one 'template' company within Ax 2009 apply two of the InventTable columns to multiple other companies.  All companies possess the same articles/items identifier data, it was just a case of updating the Item Group, and the Name Alias fields which we use as a 'translation' from the parent company - they have companies in Poland, Russia, Morocco,..

Here I was interested in learning:
  • Use a CrossCompany declaration inside the query, instead of using a ChangeCompany(...) method before calling the query.
  • Test if we could apply a transaction across companies - Yes we can!  At the database level all data in the below example sits in the same table anyway.
static void Update_InventTable()
{
    CompanyId               companyDestination, companySource;

    InventTable             tableSource, tableDestination;
    ItemId                  itemId;
    ItemGroupId             itemGrpId;
    ItemNameAlias           nameAlias;

    Dialog                  dialog;
    DialogField             field1;

    List                    lstCosicas;
    ListIterator            iterator;
    #define.splitter        (',')

    SysOperationProgress    progress = new SysOperationProgress();
    #AviFiles

    #define.companySource       ('BAI')    //curext()
    #define.companyDestination  ('AML,GLO,ITA,JML,MAP,MAR,MARR,MPOL,POL,RUS,SANT,SID,TOW')
    container               conDestination, conSource;
    int                     iCnt;
    ;

    dialog          = new Dialog("Apply inventory item update");
    dialog.addText(strfmt("¿Apply Article Group and Name Alias fields from company '%1' to:", #companySource));
    dialog.addText("     " + #companyDestination);
    field1          = dialog.addFieldValue(typeId(CompanyId), #companySource, "Choose source company");
    dialog.run();
    if (dialog.closedOK())
    {
        companySource = field1.value();
    }
    else
    {
        return;
    }

    progress.setCaption('Updatind Articles...');
    progress.setAnimation(#AviUpdate);

    // Create company containers for use in some exciting nested cross-company queries
    lstCosicas  = strSplit(#companyDestination, #splitter);
    iterator    = new ListIterator(lstCosicas);
    while (iterator.more())
    {
        companyDestination      = iterator.value();
        conDestination          += companyDestination;
        iterator.next();
    }
    conSource       += #companySource;

    // Let's update the InventTable in the multiple companies, across only one fat transaction
    ttsbegin;
    // Obtain the fields from the Source Company
    while select crosscompany : conSource ItemId, ItemGroupId, NameAlias, DataAreaId from tableSource order by ItemId
    {
        itemId      = tableSource.ItemId;
        itemGrpId   = tableSource.ItemGroupId;
        nameAlias   = tableSource.NameAlias;

        iCnt        = 0;

        setprefix(          strfmt("From Company %1, applying article '%2' across other companies", tableSource.dataAreaId, itemId) );
        progress.setText(   strfmt("From Company %1, applying article '%2' across other companies", tableSource.dataAreaId, itemId) );
        while select forupdate crosscompany : conDestination tableDestination where tableDestination.ItemId == itemId
        {
           changecompany(tableDestination.dataAreaId)
           {
                      tableDestination.ItemGroupId     = itemGrpId;
                      tableDestination.NameAlias       = nameAlias;
                      tableDestination.update();
           }
           iCnt++;
        }
        info(strfmt("Article '%2' updated in %1 companies.", iCnt, itemId));
    }
    ttsCommit;

    info('End');
}

My doubts and thoughts are:

  • Changing company has a certain 'cost' involved and so I assume applying the CrossCompany feature at the query level will be more efficient than nesting a 'find' method in a ChangeCompany?
  • How to apply the above update in an even more efficient manner? I can't see CrossCompany in the update_recordset documentation, and it explicitly states that CrossCompany is not implemented within the delete_from and insert_recordset clauses.
  • Aside from having to build the containers for the CrossCompany feature, I like it for legibility/readability within the code.  The ChangeCompany method, however, is more 'obvious' when skimming through someone else's code.  It also seems to be obligatory if you need to update the data. 

lunes, 6 de febrero de 2012

The Endless Progress Bar/Indicator


One of the most complete posts I've seen about the Progress Bar/Indicator is by that of mfp.  One tidbit of useful information we can glean from the text is that by not setting the total setTotal(...) number of iterations we won't see the progress from 0 to X but still have the animation for the user to 'enjoy' watching.  Sometimes we neither know nor care for the percentage of completion of our task.
If you do not specify the total, the progress indicator and the time remaining will not be shown.
We therefore can exclude the incCount() call as well:
    SysOperationProgress  progress = new SysOperationProgress();
    #AviFiles
    int k;
    ;
    progress.setCaption('@SYS8516'); //Updating...
    progress.setAnimation(#AviUpdate);

    // It'll be 10 passes today, 2 million in the production environment!
    for (k=0; k<10; k++) {
        progress.setText( strfmt("Update step %1", k+1) );
        sleep(2000);
    }