giovedì 16 gennaio 2020

AX 2012 - SSRS - Errore su report parameter ‘AX_CompanyName’


Mi è capitato varie volte di imbattermi nel seguente errore quando vado a stampare il primo report dopo il riavvio dei servizi di reporting. L'errore scompare dalla seconda stampa. 

"The DefaultValue expression for the report parameter ‘AX_CompanyName’ contains an error: Request for the permission of type 'System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed. (rsRuntimeErrorInExpression)"

L'errore non risulta quindi bloccante ma è piuttosto fastidioso, soprattuto quando capita ad un utente. Quello che posso suggerirvi è di modificare come segue il file rssrvpolicy.config all'interno delle cartelle di SSRS, impostando l'autorizzazione "FullTrust" al posto di "Esegui" per "Report_Expressions_Default_Permissions".

Configurazione iniziale:
<CodeGroup
class="UnionCodeGroup"
version="1"
PermissionSetName="Execution"
Name="Report_Expressions_Default_Permissions"
Description="This code group grants default permissions for code in report expressions and Code element. ">
[...]
</CodeGroup>

Configurazione modificata:

<CodeGroup 
class="UnionCodeGroup" 
version="1" 
PermissionSetName="
FullTrust
Name="Report_Expressions_Default_Permissions" 
Description="This code group grants default permissions for code in report expressions and Code element. "> 

[..] 

</CodeGroup>



Fonte:

https://community.dynamics.com/ax/b/axsupport/archive/2012/02/02/microsoft-dynamics-ax-2012-reporting-extensions-error-system-security-permissions-environmentpermission-while-running-report

AX 2012 - Job Fieldname print

In this post I'll share a job made in order to print an infolog having all the fields of a table in ax with some useful information like, field name, label, help text, data type.

 static void AGIA_TableFieldNames_2(Args _args)    
 {    
      DictTable dt           = new SysDictTable(77);    
      FieldId _fieldId     = dt.fieldNext(0);    
      DictField                _dictField;    
      Dialog                dialog;    
      DialogField           myDialogField;    
      tablename                id;    
      dialog                     = new dialog();    
      myDialogField           = dialog.addField(extendedTypeStr(tablename), "TableName : ");  
      dialog.run();   
      if(dialog.closedOk())    
      {    
           id = myDialogField.value();    
           dt = new sysdicttable(tableName2id(id));    
           info(strFmt("Field Name; Field Label; Field Type; Field Help; Field Mandatory; Field Lenght"));    
           if(_fieldId)    
           while(_fieldId)    
           {    
                _dictField =dt.fieldObject(_fieldId);   
                if(_dictField)  
                {  
                     if(!_dictField.isSystem())  
                     {  
                          info(strFmt("%1; %2; %3; %4; %5; %6", _dictField.name(),_dictField.label(),_dictField.baseType(),_dictField.help(),_dictField.mandatory(), _dictField.stringLen()));    
                          _fieldId= dt.fieldNext(_fieldId);  
                     }  
                }  
           }    
           else  
           {  
                info(strFmt("Missing table %1", id));  
           }                 
      }    
 }   
Con questo job invece possiamo stampare il contenuto di una tabella (lista dei campi e valore) in maniera dinamica:
 static void LIL_PrintTableValue(Args _args)  
 {  
   DictTable  dt;  
   DictField  dictField;  
   Common   common;  
   Counter   i=0;  
   FieldId   fieldId;  
       
   dt = new DictTable(tableNum(InventSite));  
   
   common = dt.makeRecord();  
    
   while select common  
   {  
       
     fieldId = dt.fieldNext(0);  
       
     while (fieldId)  
     {  
       dictField = dt.fieldObject(fieldId);  
         
       if(dictField.configurationKeyId() != configurationkeynum(SysDeletedObjects60))  
       {  
         info(strFmt("%1 = %2",fieldId2name(dt.id(),dictField.id()),common.(dictField.id())));  
       }  
         
       fieldId = dt.fieldNext(fieldId);  
     }  
       
     info("---------------------");  
   }  
 }  

mercoledì 26 giugno 2019

AX 2012 - Job per creazione progetto da CSV

In questo post pubblico un job che ho scritto che serve per creare un progetto privato leggendo un csv contenente una lista di tipi e nomi degli elementi dell'AOT

 static void LIL_ProjectElementAdd(Args _args)  
 {  
   TextIo         inFile;  
   container        line;  
   Counter         records;  
   SysOperationProgress  simpleProgress;  
   container        fileContainer;  
   Counter         loopCounter;  
   CustTable        CustTable;  
   InventTable       InventTable;  
   str           filename,  
               properties,  
               objectType,  
               objectName,  
               aotType,aotPath;  
   TreeNode        projectNode,  
               tempNode;  
   ProjectGroupNode    groupNode;  
   boolean         managed;  
   
   #OCCRetryCount  
   #AviFiles  
   #File  
   #aot  
   #DMF  
   
   filename = WinAPI::getOpenFileName(0,  
                 [WinAPI::fileType(#csv),#AllFilesName + #csv],  
                 strFmt(@'C:\users\%1\Desktop',WinApi::getUserName()),  
                 "@SYS53008"  
                 );  
   try  
   {  
     inFile = new TextIo(filename, 'r');  
     inFile.inRecordDelimiter('\n');  
     inFile.inFieldDelimiter(';');  
     while (inFile.status() == IO_Status::OK)  
     {  
       fileContainer += [infile.read()];  
     }  
     inFile = null;  
   }  
   catch  
   {  
     throw error(strFmt("@SYS18678", filename));  
   }  
   
   simpleProgress = SysOperationProgress::newGeneral(#aviUpdate, "Importazione...", conLen(fileContainer));  
   
   records = 0;  
   
   projectNode = SysTreeNode::createProject("MyPrivateProject");  
   
   groupNode  = projectNode.AOTadd("Object");  
   properties = groupNode.AOTgetProperties();  
   
   for (loopCounter = 2; loopCounter <= conLen(fileContainer) - 1 ; loopCounter++)  
   {  
     line = conPeek(fileContainer,loopCounter);  
     objectType = conPeek(line,1);  
     objectName = conPeek(line,2);  
   
     switch(objectType)  
     {  
       case "BaseEnum" :  
         aotType = #BaseEnums;  
         aotPath = #BaseEnumsPath;  
         managed = true;  
         break;  
   
       case "Class" :  
         aotType = #Classes;  
         aotPath = #ClassesPath;  
         managed = true;  
         break;  
   
       case "ExtendedDataType" :  
         aotType = #ExtendedDataTypes;  
         aotPath = #ExtendedDataTypesPath;  
         managed = true;  
         break;  
   
       case "Form" :  
         aotType = "Forms";  
         aotPath = #FormsPath;  
         managed = true;  
         break;  
   
   
       case "Macro" :  
         aotType = "Macros";  
         aotPath = #MacrosPath;  
         managed = true;  
         break;  
   
       case "Menu" :  
         aotType = "Menus";  
         aotPath = #MenusPath;  
         managed = true;  
         break;  
   
   
       case "Table" :  
         aotType = "Tables";  
         aotPath = #TablesPath;  
         managed = true;  
         break;  
   
       case "Map" :  
         aotType = "Maps";  
         aotPath = #TableMapsPath;  
         managed = true;  
         break;  
   
       case "Query" :  
         aotType = #Queries;  
         aotPath = #QueriesPath;  
         managed = true;  
         break;  
   
       case "SecurityDuty" :  
         aotType = "SecDuties";  
         aotPath = #SecDutiesPath;  
         managed = true;  
         break;  
   
       case "SecurityPrivilege" :  
         aotType = "SecPrivileges";  
         aotPath = #SecPrivilegesPath;  
         managed = true;  
         break;  
   
       case "SecurityPrivilege" :  
         aotType = "SecPrivileges";  
         aotPath = #SecPrivilegesPath;  
         managed = true;  
         break;  
   
       case "SecurityProcessCycle" :  
         aotType = "SecProcessCycles";  
         aotPath = #SecProcessCyclesPath;  
         managed = true;  
         break;  
   
       case "SecurityRole" :  
         aotType = "SecRoles";  
         aotPath = #SecRolesPath;  
         managed = true;  
         break;  
   
       case "SSRSReport" :  
         aotType = "SSRSReports";  
         aotPath = #SSRSReportsPath;  
         managed = true;  
         break;  
   
       case "VisualStudioProjectCSharp" :  
         aotType = "VSProjectsCShar";  
         aotPath = #VSProjectsCSharpPath;  
         managed = true;  
         break;  
   
       default:  
         managed = false;  
         warning(strFmt("Unable to add %1, %2",objectType,objectName));  
     }  
   
     if(objectName && managed)  
     {  
       tempNode = TreeNode::findNode(aotPath);  
       groupNode.addNode(tempNode.AOTfindChild(objectName));  
     }  
   }  
   projectNode.AOTsave();  
 }  

L'output sarà il seguente:


martedì 11 giugno 2019

AX 2012 - Job dimensioni finanziarie

In questo post vediamo due job per manipolare le dimensione finanziarie ed assegnare il risultato ad una riga di general journal

Il primo job, data una ledger dimension cambia l'account e valida la nuova dimensione:

 static void LIL_ChangeAccountNum(Args _args)  
 {  
   LedgerJournalTrans           ledgerJournalTrans;  
   DimensionDefault            dimensiondefault;  
   LedgerDimensionAccount         LedgerDimensionAccount, ret;  
   DimensionValidationStatus        status;  
   MainAccountNum             newMainAccountnum = "10505005";  
   
   ledgerJournalTrans = ledgerJournalTrans::findRecId(5644985326, false);  
   
   dimensiondefault = DimensionDefaultingEngine::getDefaultDimension(DimensionDefaultingEngine::getDimensionSourcesForLedgerDimension(ledgerJournalTrans.ledgerdimension));  
   
   LedgerDimensionAccount = AxdDimensionUtil::getLedgerAccountId([newMainAccountnum,newMainAccountnum]);  
     
   ret = DimensionDefaultingService::serviceCreateLedgerDimension(LedgerDimensionAccount,dimensiondefault);  
   
   status = DimensionValidation::validateByTree(ret,today(),true);  
     
   if(status == DimensionValidationStatus::Valid)  
   {  
     ledgerJournalTrans.LedgerDimension = ret;  
   }  
 }  

il secondo crea una ledger dimension contente solamente il bank account num:

 static void LIL_LedgerDimensionWhitAccountOnly(Args _args)  
 {  
   LedgerJournalTrans     ledgerJournalTrans;  
   DimensionDynamicAccount   DimensionDynamicAccount;  
     
   DimensionDynamicAccount = DimensionStorage::getDynamicAccount("BANK001", LedgerJournalACType::Bank);  
     
   ledgerJournalTrans.LedgerDimension = DimensionDynamicAccount;  
 }  


mercoledì 5 giugno 2019

D365 - Script for checking the code executed while inserting data in a table manually

In order to have a quick list of fields inserted in a form we can proceed in the following way.
First insert a record in the table we want to analyse.



At this point access the following button Option --> Record info.


Click on the button Script as reported below.

The following txt document is created, in which the procedures for inserting a row in the table previously filled manually are retrieved.



lunedì 20 maggio 2019

AX 2012 - Creare batch job tramite x++ e aggiunta dipendenze

In questo post vediamo come creare dei batch job schedulabili e suddividere l'esecuzione in task. Sfruttando inoltre le "Batch Dependency" faremo in modo che ogni task parta solo se il precedente termina ( oppure và in errore ). Allo scopo creiamo una semplice classe sysoperation (LIL_WhoAmIService più relativa controller / contract) che prende come parametro un intero e lo restiuisce tramite info log. Il job seguente effettua tutte le operazioni di schedulazione / aggiunta dipendenze:

 static void LIL_TestRunDependency(Args _args)  
 {  
   BatchHeader       batchHeader   = BatchHeader::getCurrentBatchHeader();  
   List          list      = new List(Types::Class);  
   ListEnumerator     le;  
   LIL_WhoAmIController  batchTask,prev;  
   Counter         i;  
   BatchInfo        batchInfo;  
   
   batchHeader = batchHeader::construct();  
   
   batchHeader.parmCaption("Task dependency execution");  
   
   //creo 10 instaze della mia classe, ciascuna con il suo id  
   for(i = 0; i < 10; i++)  
   {  
     batchTask = new LIL_WhoAmIController(classStr(LIL_WhoAmIService),  
                        methodStr(LIL_WhoAmIService, printWhoAmi));  
   
     batchTask.parmWhoAmi(i);  
   
     batchTask.init();  
   
     //in caso di sysoperation è neccessario impostare execution mode = Synchronous  
     //altrimenti le dipendence non vengono rispettate e i task partono tutti  
     //allo stesso momento  
     batchTask.parmExecutionMode(SysOperationExecutionMode::Synchronous);  
   
     batchInfo = batchTask.batchInfo();  
   
     batchInfo.parmCaption(strFmt("Task N%1",batchTask.parmWhoAmi(i)));  
   
     list.addStart(batchTask);  
   }  
   
   //Creazione dipendenze  
   le = list.getEnumerator();  
   while (le.moveNext())  
   {  
     batchTask = le.current();  
   
     if(!prev)  
     {  
       //il primo task non avarà voncoli  
       batchHeader.addRuntimeTask(batchTask,BatchHeader::getCurrentBatchTask().RecId);  
     }  
     else  
     {  
       //i successivi dipendono dal precedente  
       batchHeader.addRuntimeTask(batchTask,BatchHeader::getCurrentBatchTask().RecId);  
       batchHeader.addDependency(prev,batchTask,BatchDependencyStatus::FinishedOrError);  
     }  
   
     prev = le.current();  
   }  
   
   batchHeader.save();  
   
   info("Done");  
 }  

Se lanciamo il job ed aspettiamo la fine dell'esecuzione dei 10 task, possiamo vedere il risultato in batch history:



L'esecuzione ha generato 10 tasks. il Task "N0" come vediamo non ha condizioni. Tutti gli altri dipendono dal precedente, Ad esempio il task N5 dipende dal task N4 e così via.

sabato 27 aprile 2019

D365 FO - creare un nuovo report ed aggiungerlo alla gestione stampa

In questo post vediamo come aggiungere un nuovo report custom alla gestione stampa (print management). Andremo a creare un semplice report delle righe ordini fornitori. Per ogni fornitore verrà generato un report con le sue righe che potrà poi essere spedito per email a ciascun fornitore.
Cominciamo:

1) create l'oggetto report che chiameremo LIL_TestReport e definite il design

2) create la classe data contract LIL_TestReportContract:


 [  
   DataContractAttribute,  
   SysOperationGroupAttribute('PrintManagementGrp'," ",'1')  
 ]  
 class LIL_TestReportContract  
 {  
   boolean usePrintManagement;  
   RecId  recordId;  
   
   [  
     DataMemberAttribute('UsePrintManagement'),  
     SysOperationLabelAttribute(literalStr("@SYS93922")),  
     SysOperationHelpTextAttribute(literalStr("@SYS318700")),  
     SysOperationGroupMemberAttribute('PrintManagementGrp'),  
     SysOperationDisplayOrderAttribute('1')  
   ]  
   public boolean parmUsePrintManagement(boolean _usePrintManagement = usePrintManagement)  
   {  
     usePrintManagement = _usePrintManagement;  
     return usePrintManagement;  
   }  
   
   [DataMemberAttribute('RecordId')]  
   public RecId parmRecordId(RecId _recordId = recordId)  
   {  
     recordId = _recordId;  
     return recordId;  
   }  
 }  

3) create la classe controller LIL_TestReportController che estende GiroPrintMgmtFormLetterController. oltre ai soliti metodi sarà obbligatorio implementare il metodo runPrintMgmt

 class LIL_TestReportController extends GiroPrintMgmtFormLetterController  
 {  
   LIL_TestReportContract contract;  
   
   public PaymentStub getGiroType()  
   {  
     return PaymentStub::None;  
   }  
   
   public boolean showIndexFields(TableId id)  
   {  
     return false;  
   }  
   
   public SRSReportName getReportName()  
   {    
     SRSReportName ret;  
   
     ret = ssrsReportStr(LIL_TestReport, Report);  
    
     return ret;  
   }  
   
   public static LIL_TestReportController construct()  
   {  
     return new LIL_TestReportController();  
   }  
   
   protected void initFormLetterReport()  
   {  
     formLetterReport = FormLetterReport::construct(PrintMgmtDocumentType::LILTestReport);  
     formLetterReport.parmPrintType(PrintCopyOriginal::OriginalPrint);  
     formLetterReport.parmReportRun().parmCheckScreenOutput(true);  
   
     contract = this.parmReportContract().parmRdpContract() as LIL_TestReportContract;  
   
     super();  
   }  
   
   protected void runPrintMgmt()  
   {  
     Query  q = this.getFirstQuery();  
     QueryRun queryRun;  
     VendTable vendTable;  
   
     formLetterReport.parmDefaultCopyPrintJobSettings(this.getReportContract().parmPrintSettings());  
     formLetterReport.parmDefaultOriginalPrintJobSettings(this.getReportContract().parmPrintSettings());  
     formLetterReport.parmUsePrintMgmtDestinations(contract.parmUsePrintManagement());  
   
     q.dataSourceTable(tableNum(VendTable))  
       .addSortField(fieldNum(VendTable,AccountNum), SortOrder::Ascending);  
   
     queryRun = new QueryRun(q);  
   
     while (queryRun.next() && !this.parmCancelRun())  
     {  
       if (queryRun.changed(tableNum(VendTable)))  
       {  
         vendTable = queryRun.get(tableNum(VendTable));  
   
         contract.parmRecordId(vendTable.RecId);  
           
         formLetterReport.loadPrintSettings(vendTable, vendTable, vendTable.languageId(),vendTable.AccountNum);  
   
         this.parmReportContract().parmRdlContract().parmLanguageId(vendTable.languageId());  
         this.parmReportContract().parmRdlContract().parmLabelLanguageId(vendTable.languageId());  
   
         this.outputReports();  
       }  
     }  
   }  
   
   public static void main(Args _args)  
   {  
     LIL_TestReportController controller = LIL_TestReportController::construct();  
   
     controller.parmReportName(controller.getReportName());  
     controller.parmArgs(_args);  
     controller.parmDialogCaption("@SYS95972");  
     controller.startOperation();  
   }  
   
 }  

Come vediamo la chiamata outputReport viene fatta ogni volta che cambia il codice del fornitore. Questo serve a generare la "spaccatura" per vendor account

4)create la query LIL_TestQuery che conterrà come datasource purchLine + vendTable

5) create la tabella temporanea LIL_TestReportTmp

6) create la dataProvider LIL_TestReportDP. Nel metodo processreport della data provider imposteremo il filtro per vendor account

 public void processReport()  
   {  
     PurchLine              purchLine;  
     LIL_TestReportContract       contract = this.parmDataContract() as LIL_TestReportContract;  
     RecId                vendTableRecId = contract.parmRecordId();  
     VendTable              vendTable = VendTable::findRecId(vendTableRecId);  
     Query                q = this.parmQuery();  
   
     q.dataSourceNo(1).clearRange(fieldNum(PurchLine, VendAccount));  
     q.dataSourceNo(1).addRange(fieldNum(PurchLine, VendAccount)).value(vendTable.AccountNum);  
   
     QueryRun queryRun      = new QueryRun(this.parmQuery());  
   
     LIL_TestReportTmp.setConnection(this.parmUserConnection());  
   
     while(queryRun.next())  
     {  
       purchLine = queryRun.get(tableNum(PurchLine));  
   
       this.insertIntoTmpTable(purchLine, vendTable);  
     }  
   }  

7) create il menù LIL_TestReport item di tipo output impostando
  • object type = class
  • object         = LIL_TestReportController
ed inserirlo a menù

9) Estendere l'enum PrintMgmtDocumentType aggiungiundo un nuovo elemento LILTestReport

10) create la classe LIL_PurchFormLetterReport_TestReport che estende PurchFormLetterReport e decorarla col nuovo valore dell'enum:

 [PrintMgmtDocumentTypeFactoryAttribute(PrintMgmtDocumentType::LILTestReport)]  
 class LIL_PurchFormLetterReport_TestReport extends PurchFormLetterReport  
 {  
   protected container getDefaultPrintJobSettings(PrintSetupOriginalCopy _printCopyOriginal)  
   {  
     SRSPrintDestinationSettings printSettings = PrintMgmtSetupSettings::initDestination();  
   
     printSettings.printMediumType(SRSPrintMediumType::Screen);  
     return printSettings.pack();  
   }  
   
   public PrintMgmtDocumentType getPrintMgmtDocumentType()  
   {  
     return PrintMgmtDocumentType::LILTestReport;  
   }  
   
   protected PrintMgmtHierarchyType getPrintMgmtHierarchyType()  
   {  
     return PrintMgmtHierarchyType::Purch;  
   }  
   
   protected PrintMgmtNodeType getPrintMgmtNodeType()  
   {  
     return PrintMgmtNodeType::VendTable;  
   }  
 }  

La gestione del report termina quì, le modifiche seguenti servono per inserire il nuovo report nel print management standard. Dato che è un report che riguarda la contabilità fornitori andremo ad inserire il report nel print management del modulo fornitori.

11) Creare la classe LIL_PrintMgmtDocTypeDelegateHandler e sottoscrivere i seguenti delegati così:

 class LIL_PrintMgmtDocTypeDelegateHandler  
 {  
   [SubscribesTo(classStr(PrintMgmtDocType), delegateStr(PrintMgmtDocType, getDefaultReportFormatDelegate))]  
   public static void PrintMgmtDocType_getDefaultReportFormatDelegate(PrintMgmtDocumentType _docType, EventHandlerResult _result)  
   {  
     switch (_docType)  
     {  
       case PrintMgmtDocumentType::LILTestReport:  
         _result.result(LIL_PrintMgmtDocTypeDelegateHandler::getDefaultReportFormat(_docType));  
         break;  
           
     }  
   
   }

   [SubscribesTo(classStr(PrintMgmtDocType), delegateStr(PrintMgmtDocType, getQueryTableIdDelegate))]
   public static void PrintMgmtDocType_getQueryTableIdDelegate(PrintMgmtDocumentType _docType, EventHandlerResult _result)
   {
       switch(_docType)
       {
           case PrintMgmtDocumentType::TEST:
               _result.result(tableNum(CustPackingSlipJour));
               break;
       }
   }  
   
   private static PrintMgmtReportFormatName getDefaultReportFormat(PrintMgmtDocumentType _docType)  
   {  
     switch (_docType)  
     {  
       case PrintMgmtDocumentType::LILTestReport:  
         return ssrsReportStr(LIL_TestReport, Report);  
         break;  
     }  
   
     throw Exception::Internal;  
   }  
   
   [SubscribesTo(classStr(PrintMgmtDocType), delegateStr(PrintMgmtDocType, getPartyTypeDelegate))]  
   public static void PrintMgmtDocType_getPartyTypeDelegate(PrintMgmtDocumentType _docType, Common _jour, EventHandlerResult _result)  
   {  
     PrintMgmtPrintDestinationPartyType ret;  
   
     switch(_docType)  
     {  
       case PrintMgmtDocumentType::LILTestReport:  
         ret = PrintMgmtPrintDestinationPartyType::Vendor;  
         break;  
   
       default:  
         ret = _result.result();  
     }  
   
     _result.result(ret);  
   }  
   
   [SubscribesTo(classStr(PrintMgmtDocType), delegateStr(PrintMgmtDocType, getEmailAddressDelegate))]  
   public static void PrintMgmtDocType_getEmailAddressDelegate(PrintMgmtDocumentType _docType, SrsPrintDestinationToken _purpose, Common _jour, PrintMgmtPrintDestinationTokens _printMgmtPrintDestinationTokens, EventHandlerResult _result)  
   {  
     str ret;  
       
     switch(_docType)  
     {  
       case PrintMgmtDocumentType::LILTestReport:  
         ret = "test123@gmail.com";//inserire quì eventuali logiche per reperire gli indirizzi e mail  
         break;  
     
       default:  
         ret = _result.result();  
     }  
   
     _result.result(ret);  
   }  
   
 }  

12) creare la classe LIL_PrintMgmtNode_PurchHandler:

 class LIL_PrintMgmtNode_PurchHandler  
 {  
   [PostHandlerFor(classStr(PrintMgmtNode_Purch), methodStr(PrintMgmtNode_Purch, getDocumentTypes))]  
   public static void PrintMgmtNode_Purch_Post_getDocumentTypes(XppPrePostArgs args)  
   {  
     List docTypes;  
   
     docTypes = args.getReturnValue() as List;  
   
     docTypes.addEnd(PrintMgmtDocumentType::LILTestReport);  
   
     args.setReturnValue(docTypes);  
   }  
   
 }  

13) creare la classe LIL_PrintMgmtNode_VendTableHandler:

 class LIL_PrintMgmtNode_VendTableHandler  
 {  
   [PostHandlerFor(classStr(PrintMgmtNode_VendTable), methodStr(PrintMgmtNode_VendTable, getDocumentTypes))]  
   public static void PrintMgmtNode_VendTable_Post_getDocumentTypes(XppPrePostArgs args)  
   {  
     List docTypes;  
   
     docTypes = args.getReturnValue() as List;  
   
     docTypes.addEnd(PrintMgmtDocumentType::LILTestReport);  
   
     args.setReturnValue(docTypes);  
   }  
   
 }  

14) creare la classe LIL_PrintMgmtReportFormatPopulator_Extension

[ExtensionOf(classStr(PrintMgmtReportFormatPopulator))]
final class LIL_PrintMgmtReportFormatPopulator_Extension
{
    protected void addDocuments()
    {
        next addDocuments();

        this.addStandard(PrintMgmtDocumentType::TEST);
    }
}  

A questo punto se tutto è andato a buon fine, aprendo Account payabale -> setups -> form -> form setup e cliccando su "Print management" dovremmo vedere il nostro report così:



se lanciamo il report col flag print management spento verrano costruiti i print settings di default specificati in LIL_TestReportController (quindi screen):



Notare che dato che abbiamo lanciato il report per due ordini con due diversi fornitori vengono generati due report diversi, uno per il fornitore fabrikam (purchase order 00000041 di quattro righe) e uno per contoso office (purchase order 00000042 di una riga). Se lanciamo il report attivando "Use print management destination" l'output sarà un file pdf come specificato nei print settings.

Se vogliamo aggiungere un nuovo report di tipo "documentale" la nostra controller dovrà estendere TradeDocumentReportController (per questo esempio usiamo un nuovo documentstatus "TEST")
Ho preso come esempio le classi della stampa del DDT.
Oltre alle classi che abbiamo usato prima dobbiamo aggiungere le seguenti: 


 [DataContractAttribute]  
 [DocumentStatusFactoryAttribute(DocumentStatus::TEST)]  
 class TEST_SalesFomLetterTestContract extends SalesFormLetterContract  
 {  
   public DocumentStatus getDocumentStatus()  
   {  
     return DocumentStatus::TEST;  
   }  
 }  

 [DocumentStatusFactoryAttribute(DocumentStatus::TEST)]  
 [SysOperationJournaledParametersAttribute(true)]  
 class    TEST_SalesFormLetter_TEST  
 extends   SalesFormLetter  
 {  
   
   public ClassDescription caption()  
   {  
     return TEST_SalesFormLetter_TEST::description();  
   }  
   
   public DocumentStatus documentStatus()  
   {  
     return DocumentStatus::TEST;  
   }  
   
   void new(  
     IdentifierName _className = classStr(FormletterService),  
     IdentifierName _methodName= methodStr(FormletterService, postSalesOrderConfirmation),  
     SysOperationExecutionMode _executionMode = SysOperationExecutionMode::Synchronous)  
   {  
     super(_className, _methodName, _executionMode);  
   }  
   
   protected PrintMgmtDocumentType printMgmtDocumentType()  
   {  
     return PrintMgmtDocumentType::TEST;  
   }  
   
   public void resetParmListCommonCS()  
   {  
     super();  
   
     lockSalesUpdate = true;  
   }  
   
   boolean unpack(container _packedClass)  
   {  
     Integer version = conPeek(_packedClass,1);  
     Integer dlvDays;  
     Code   dlvSpec;  
   
     #LOCALMACRO.ParmList_v25sp2  
       parmId,  
       salesParmUpdate.proforma,  
       salesParmUpdate.specQty,  
       dlvSpec,  
       dlvDays,  
       printout,  
       printFormletter,  
       printerSettingsFormletter  
     #ENDMACRO  
   
     #LOCALMACRO.ParmList_v30  
       parmId,  
       salesParmUpdate,  
       printout,  
       printFormletter,  
       printerSettingsFormletter  
     #ENDMACRO  
   
     #LOCALMACRO.ParmList_v401  
       parmId,  
       salesParmUpdate,  
       printout,  
       printFormletter,  
       printerSettingsFormletter,  
       printerSettingsFormletterCopy  
     #ENDMACRO  
   
     #LOCALMACRO.ParmList_v5  
       parmId,  
       salesParmUpdate,  
       printout,  
       printFormletter,  
       printerSettingsFormletter,  
       printerSettingsFormletterCopy,  
       usePrintManagement  
     #ENDMACRO  
   
     ParmId     parmId;  
     SalesParmUpdate salesParmUpdate;  
     Printout    printout;  
     NoYes      printFormletter;  
     container    printerSettingsFormletter;  
     container    printerSettingsFormletterCopy;  
     boolean     usePrintManagement;  
   
     switch (version)  
     {  
       case 6+1 /*case is old currentversion + old parentversion*/ :  
         [version, #parmList_v5] = _packedClass;  
         this.setNewContract(SalesFormLetterContract::construct(DocumentStatus::TEST));  
   
         contractIsFromPreviousVersion = true;  
         this.parmId(parmId);  
         this.salesParmUpdate(salesParmUpdate);  
         this.printout(printout);  
         this.printFormLetter(printFormletter);  
         this.updatePrinterSettingsFormLetter(printerSettingsFormletter, PrintSetupOriginalCopy::Original);  
         this.updatePrinterSettingsFormLetter(printerSettingsFormletterCopy, PrintSetupOriginalCopy::Copy);  
         this.usePrintManagement(usePrintManagement);  
         break;  
   
       case 5         :  [version, #ParmList_v401]        = _packedClass;  
                     break;  
   
       case 4         :  [version, #ParmList_v30]        = _packedClass;  
                     printerSettingsFormletterCopy      = printerSettingsFormletter;  
                     break;  
   
       case 2         :  [version, #ParmList_v25sp2]       = _packedClass;  
                     break;  
   
       default :  
                     return super(_packedClass);  
     }  
   
     return true;  
   }  
   
   protected boolean canRunInNewSession()  
   {  
     return this.checkRunInNewSession();  
   }  
   
   private static ClassDescription description()  
   {  
     return "TEST";  
   }  
   
   [SysObsolete('Use SalesFormLetter::construct() instead.')]  
   static public SalesFormLetter_Confirm newConfirm(  
     IdentifierName _className = classStr(FormletterService),  
     IdentifierName _methodName = methodStr(FormletterService, postSalesOrderConfirmation),  
     SysOperationExecutionMode _executionMode = SysOperationExecutionMode::Synchronous)  
   {  
     return SalesFormLetter::construct(DocumentStatus::Confirmation, _className, _methodName, _executionMode);  
   }  
   
 }  

 [PrintMgmtDocumentTypeFactoryAttribute(PrintMgmtDocumentType::TEST)]  
 [DocumentStatusFactoryAttribute(DocumentStatus::TEST)]  
 class TEST_SalesFormLetterReport_TEST extends SalesFormLetterReport  
 {  
   protected container getDefaultPrintJobSettings(PrintSetupOriginalCopy _printCopyOriginal)  
   {  
     return SalesFormLetter::getPrinterSettingsFormletter(DocumentStatus::TEST, _printCopyOriginal);  
   }  
   
   public PrintMgmtDocumentType getPrintMgmtDocumentType()  
   {  
     return PrintMgmtDocumentType::TEST;  
   }  
   
   protected PrintMgmtHierarchyType getPrintMgmtHierarchyType()  
   {  
     return PrintMgmtHierarchyType::Sales;  
   }  
   
   protected PrintMgmtNodeType getPrintMgmtNodeType()  
   {  
     return PrintMgmtNodeType::CustTable;  
   }  
   
 }  

 [DocumentStatusFactory(DocumentStatus::TEST)]  
 class TEST_SelesQty_TEST extends SalesQuantity_PackingSlip  
 {  
 }  

ecco infine la nostra controller:

 class TEST_Controller extends TradeDocumentReportController  
 {  
   FormletterJournalPrint       formletterJournalPrint;  
   CustPackingSlipJour         CustPackingSlipJour;  
   
   protected str documentTitle()  
   {  
     str documentTitle;  
   
     documentTitle = "Test report";  
   
     return documentTitle;  
   }  
   
   protected boolean getFirstJournal()  
   {  
     return journalList.first(CustPackingSlipJour);  
   }  
   
   protected boolean getNextJournal()  
   {  
     return journalList.next(CustPackingSlipJour);  
   }  
   
   protected RecId getRecordId()  
   {  
     return CustPackingSlipJour.RecId;  
   }  
   
   protected void output()  
   {  
     formLetterReport.loadPrintSettings(CustPackingSlipJour, SalesTable::find(CustPackingSlipJour.SalesId), CustPackingSlipJour.LanguageId);  
     this.parmReportContract().parmRdlContract().parmLanguageId(CustPackingSlipJour.LanguageId);  
     this.parmReportContract().parmRdlContract().parmLabelLanguageId(CustPackingSlipJour.LanguageId);  
   
     super();  
   }  
   
   protected void initFormLetterReport()  
   {  
     printCopyOriginal = this.parmArgs().parmEnum();  
   
     if (classIdGet(this.parmArgs().caller()) == classNum(SalesPackingSlipJournalPrint))  
     {  
       // Set the caller  
       formletterJournalPrint = this.parmArgs().caller();  
     }  
   
     if (this.parmArgs().record())  
     {  
       // Get journal list from the selected record/s  
       journalList = FormLetter::createJournalListCopy(this.parmArgs().record());  
     }  
     else  
     {  
       journalList = this.parmArgs().object();  
     }  
   
     formLetterReport = FormLetterReport::construct(PrintMgmtDocumentType::TEST);  
   
     formLetterReport.parmPrintType(printCopyOriginal);  
   
     if (formletterJournalPrint)  
     {  
       // Get the print settings  
       formLetterReport.parmDefaultCopyPrintJobSettings(new SRSPrintDestinationSettings(formletterJournalPrint.parmPrinterSettingsFormLetterCopy()));  
       formLetterReport.parmDefaultOriginalPrintJobSettings(new SRSPrintDestinationSettings(formletterJournalPrint.parmPrinterSettingsFormLetter()));  
       formLetterReport.parmUsePrintMgmtDestinations(formletterJournalPrint.parmUsePrintManagement());  
     }  
     else if (printCopyOriginal == PrintCopyOriginal::OriginalPrint)  
     {  
       // Always use the print mgmt destinations when reprinting for the OriginalPrint case.  
       formLetterReport.parmUsePrintMgmtDestinations(true);  
     }  
   
     super();  
   }  
   
   protected void setDataContractRecord(Common _common)  
   {  
     CustPackingSlipJour = args.record();  
   }  
   
   public static TEST_Controller construct()  
   {  
     return new TEST_Controller();  
   }  
   
   public static void main(Args _args)  
   {  
     SrsReportRunController formLetterController = TEST_Controller::construct();  
   
     TEST_Controller controller = formLetterController;  
     controller.initArgs(_args, PrintMgmtDocType::construct(PrintMgmtDocumentType::TEST).getDefaultReportFormat());  
   
     formLetterController.startOperation();  
   }  
   
 }  

In questo modo creando due menu item distinti possiamo fare la stessa cosa che fà il DDT dal giornale ( cioè il tasto che lancia il report a video e quello che usa il print management). Possiamo inoltre gestire il print magement in fase di posting( in questo caso il report è stato collegato alla stampa del DDT (SalesPackingslipJournalPrint)

In Allegato Quì il pacchetto di tutto il progetto