Monday, July 15, 2019

D365FO - Copy custom value from Accounts Payable Invoice Register (LedgerJournalTrans) to Invoice Approval (VendTrans)

When creating a accounts payable invoice register entry it will create a record in the table LedgerJournalTrans. Whenever you post this journal it will create a record for the journal in the VendTrans table.

If you go into the Invoice Approval form and click on "Find Vouchers" you will see that any custom fields you added to LedgerJournalTrans are available. However once one of these vouchers are loaded into the approval form you will find that the values are longer available. This is because it has converted your LedgerJournalTrans instance into its own VendTrans table instance.

In order to copy the values from LedgerJournalTrans to VendTrans you will need to extend the VendVoucher class which is shown below. It is good to note that in theory this method should be able to be applied to the CustVoucher (customer invoices) as it applies the same structure (CustVendTrans map) and using the common table method to pass VendTrans or CustTrans. 


[ExtensionOf(classStr(VendVoucher))]
final class APInvoiceModsVendVoucher_Extension
{
    

    /// <summary>
    /// COC of initCustVendTrans which sets the initial values of vendTrans from ledgerjournaltrans
    /// </summary>
    /// <param name = "_custVendTrans">VendTrans</param>
    /// <param name = "_ledgerPostingJournal">LedgerVoucher</param>
    /// <param name = "_useSubLedger">Use sub ledger default is false</param>
    protected void initCustVendTrans(
                                    CustVendTrans _custVendTrans,
                                    LedgerVoucher _ledgerPostingJournal,
                                    boolean _useSubLedger)
    {
        //execute the base functionality
        next initCustVendTrans(_custVendTrans, _ledgerPostingJournal, false);
        
        //get vendTrans table buffer form CustVendTrans map instance
        VendTrans vendTrans = _custVendTrans as VendTrans;
        
        //if the common instance is being initialized via the ledgerjournaltrans table we need to copy the custom field
        if (common && common.TableId == tableNum(LedgerJournalTrans))
        {
            LedgerJournalTrans ledgerJournalTrans = common;
            
            //copy internal invoice notes and the description from the ledgerjournal trans to the vendtrans
            vendTrans.CustomField = ledgerJournalTrans.CustomField;
            //this is not a custom field but the values do not get transfered
            vendTrans.Txt = ledgerJournalTrans.Txt;
        }
    }

}

D365FO / Setting default values for custom fields on a vendor invoice from a purchase order (VendInvoiceInfoTable / PurchFormletterParmDataInvoice)

When creating a vendor invoice and you are trying to populate custom fields on table VendInvoiceInfoTable you may find that the following will only work if you are creating a blank invoice and not executing it from the invoice create button on a purchase order



[DataEventHandler(tableStr(VendInvoiceInfoTable), DataEventType::InitializingRecord)] 
 public static void VendInvoiceInfoTable_onInitializingRecord(Common sender, DataEventArgs e) 
 { 
      VendInvoiceInfoTable vendInvoiceInfoTable = sender as VendInvoiceInfoTable; 
      vendInvoiceInfoTable.CustomField   VendParameters::find().DefaultCustomField; 
 }

This is due to the class PurchFormletterParmDataInvoice.createInvoiceHeaderFromTempTable()
which called .skipEvents(true), skipDataMethods(true), .skipDatabaseLog(true) and calls a Query::insert_recordset() because of this none of the default events will get triggered.

In order to properly populate customfields on a blank vendor invoice or creating once from a purchase order the following will need to be done via extensions and CoC.


  1. Add custom fields to VendInvoiceInfoTable
  2. Add custom fields to VendInvoiceInfoTableTmp (name and types need to match what was created on VendInvoiceInfoTable)
    1. We have to add it to this table because on PurchFormLetterParmDataInvoice.insertParmTable() it calls a buf2buf command which loops through the field list.
  3. Create extension class of table VendInvoiceInfoTable and apply a CoC of defaultRow method which will populate the value onto both the regular and tmp instance of VendInvoiceInfoTable however it will not save to the database unless the remaining steps are completed
    1. /// <summary>
      /// Default values for a vendor invoice header from a purchase order
      /// </summary>
      [ExtensionOf(tableStr(VendInvoiceInfoTable))]
      final class APInvoiceModsVendInvoiceInfoTable_Extension
      {
          /// <summary>
          /// COC table method defaultRow which resets default valies
          /// </summary>
          /// <param name = "_purchTable">PurchTable</param>
          /// <param name = "_ledgerJournalTrans">LedgerJournalTrans</param>
          /// <param name = "_resetFieldState">Reset field state</param>
          public void defaultRow(PurchTable _purchTable, LedgerJournalTrans _ledgerJournalTrans, boolean _resetFieldState)
          {
              CustomEDTType defaultCustomField;
              
      next defaultRow(_purchTable,_ledgerJournalTrans,_resetFieldState);

              //find the default custom field from vend parms
      defaultCustomField = VendParameters::find().DefaultCustomField;
              
      //save the default invoice type to the header which will get copied to    VendInvoiceInfoTableTmp and then back to the main table due to PurchFormLetterParmDataInvoice.createInvoiceHeaderFromTempTable
      this.CustomField = defaultCustomField;

          }

      }
  4. Create extension class of PurchFormLletterParmDataInvoice and apply a CoC to buildCreateInvoiceHeaderFromTempTableFieldQuery and buildCreateInvoiceHeaderFromTempTableFieldMap because the method PurchFormletterParmDataInvoice.createInvoiceHeaderFromTempTable() creates a dynamic map of the fields to copy from the temp table into the main table 
    1. [ExtensionOf(classStr(PurchFormletterParmDataInvoice))]
      final class APInvoiceModsPurchFormletterParmDataInvoice_Extension
      {
          /// <summary>
          /// COC method that adds our custom field to the Field Query selection  from the temp table
          /// </summary>
          /// <param name = "_qbdsVendInvoiceInfoTableTmp">VendInvoiceInfoTableTmp</param>
          protected void buildCreateInvoiceHeaderFromTempTableFieldQuery(QueryBuildDataSource _qbdsVendInvoiceInfoTableTmp)
          {
              next buildCreateInvoiceHeaderFromTempTableFieldQuery(_qbdsVendInvoiceInfoTableTmp);

              //add custom selection field
      _qbdsVendInvoiceInfoTableTmp.addSelectionField(fieldNum(VendInvoiceInfoTableTmp, CustomField));
          }

          /// <summary>
          /// COC method thats adds our custom field to the dynamic field map against the main table. Which will get copied from the selection query
          /// </summary>
          /// <param name = "_qbdsVendInvoiceInfoTableTmp">VendInvoiceInfoTableTmp</param>
          /// <returns>Modified dynamic map to insert into the db</returns>
          protected Map buildCreateInvoiceHeaderFromTempTableFieldMap(QueryBuildDataSource _qbdsVendInvoiceInfoTableTmp)
          {
              var targetToSourceMap = next buildCreateInvoiceHeaderFromTempTableFieldMap(_qbdsVendInvoiceInfoTableTmp);
              targetToSourceMap.insert(fieldStr(VendInvoiceInfoTable, CustomField), [_qbdsVendInvoiceInfoTableTmp.uniqueId(), fieldStr(VendInvoiceInfoTableTmp, CustomField)]);
             
      return targetToSourceMap;
           }

      }

By applying our populate method to the defaultRow() method this will also trigger the value to populated on a blank vendor invoice once a vendor account is selected.