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