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

mercoledì 6 marzo 2019

AX 2012 - Impostare il filtro di una query di un report in base ai record selezionati su una grid

Molto spesso capita di dover lanciare un report filtrandolo per i record selezionati su un grid. In questo esempio vogliamo stampare il report del giornale/i inventariale selezionati. Nel metodo prePromptModifyContract() della nostra controller possiamo creare un metodo set range che costruisce il range in base ai record selezionati dall'utente sul grid:

 public void setRanges(Query _query)  
 {  
   QueryBuildDataSource    qbds;  
   InventJournalTable     inventJournalTable;  
   FormDataSource       fds;  
   
   qbds = _query.dataSourceTable(tablenum(InventJournalTable));  
   
   while (qbds.findRange(fieldNum(InventJournalTable, JournalId)))  
   {  
     qbds.clearRange(fieldNum(InventJournalTable, JournalId));  
   }  
   
   if (this.parmArgs())  
   {  
     switch (this.parmArgs().dataset())  
     {  
       case tableNum(InventJournalTable):  
   
         inventJournalTable = this.parmArgs().record();  
   
         fds = inventJournalTable.dataSource();  
   
         if (inventJournalTable.isFormDataSource() && inventJournalTable.dataSource() && fds.anyMarked())  
         {  
           for (inventJournalTable = fds.getFirst(fds.anyMarked()); inventJournalTable; inventJournalTable = fds.getNext())  
           {  
             qbds.addRange(fieldNum(InventJournalTable, JournalId)).value(inventJournalTable.JournalId);  
           }  
         }  
         else  
         {  
           //nessun record selezionato, prendi il record corrente  
           qbds.addRange(fieldNum(InventJournalTable, JournalId)).value(inventJournalTable.JournalId);  
         }  
         break;  
   
       default:  
         throw error(strFmt("@SYS19306",funcName()));  
     }  
   }  
 }  


mercoledì 20 febbraio 2019

AX2009 - Picking - Packing in one shot!


Process:
1.        Generate a sales order: create a sales order accessing the following path Accounts receivable --> Common --> Sales order. Create a sales order header defining the customer, address and contact information. Define if the item on hand allocation should be performed automatically or manually for the sales order lines in the sales order header. Performing the reservation in an automatic way the system will apply directly the FIFO rule, in order to do it there is an enum value in the setup tab of the sales order header named Reservation (setting it to automatic the reservation will be automatically driven). Create all the sales lines related to the specific sales order header according to the items we want to ship, define also the requested delivery dates for each line. In case we decide to modify or to apply the automatic reservation of sales order lines in a second moment we can access the release sales order line for picking at the path: Inventory management --> Periodic --> Release sales order picking. At this point we can choose which items to reserve / release for picking. In order to reserve lines we can click on the button Activation and choose for the selected lines between three options:
o    All orders that can be fully delivered
o    All the orders that are physical reserved
o    All the orders that not require manual allocation of the stock
Perfoming the allocation the system will try to physical reserve all the lines selected according to FIFO rule of stock keeping.
Once we have activated all the lines we can release for picking all the activated lines clicking on the button Release for pick, at this point the system will create a picking list and a shipment in status registered.
2.        Check of possible shortages in the shipment: once we have created the shipment we can always check if there is a lack of items into the warehouse for performing the shipment clicking the shipment form (inventory management --> common --> shipments) on the button functions --> possible shortage, in case we need to reserve something we can perform the reservation manually for each shipment line or automatically clicking on the button functions --> reserve now
3.        Activation of the shipment: in order to activate the shipment it's needed to have all the stock available for the shipment lines. For doing it we have to click on the button Functions --> Activate.
4.        Picking route generation -- rules: at the activation of the shipment the system will generate the picking routes, the system has to generate a picking route for each full pallet needed to pick. And a generic picking route for all the lines remaining or considered as mixed pallet. In order to understand if a line is full pallet we need to consider if the picking quantity is a multiple of the pallet quantity defined in the item master. In case not we have to define the maximum mutiple applicable and the rest of the line will be considered as part of the picking route for mixed pallets generation. Consider to create a picking list for each full pallet to be picked in the warehouse.
5.        Picking route closing and definition of the palletid: at the closure of the picking route we can close directly each picking line defining the pallet in which we will put each wmsordertrans.
6.        Packing slip generation: Once we have closed all the picking lines we can create the packing slip, after declaring the shipment as sent clicking on the button Functions --> Send.

Demonstration in AX2009
In Order to replicate the Subprocess 4. we manage an already generated picking route as reported above, we should access the following path Inventory management --> Common --> Picking Routes.


In this form it is possible to create a picking pallet clicking (after selecting the proper picking route) on the button Create picking pallet.


Then it is possible to assign a WMSOrdertrans line to the pallet picking.


This operation should be performed line by line, it is then possible after clicking on approval details to approve line by line the picking and the pallet picking.


In the same form it is possible to split lines and assign per each line a different pick palletid.

At the end of this procedure the system will ask to deliver items to the final location.

Then entering in the shipment form we should perform the shipment staging as reported below.


We can ship items starting from the shipment form as reported below.







venerdì 8 febbraio 2019

AX 2012 - Cross reference update batch job

In AX i riferimenti incrociati (cross reference) non possono essere messi in batch. Questo job crea in automatico la ricorrenza nel batch job per schedulare giornalmente l'aggiornamento

 static void LIL_UpdateCrossRefBatch(Args _args)  
 {  
   xRefUpdate::truncateXrefTables();  
   
   xRefUpdateIL::updateAllXref(true, false, true);  
   
   info("Done, cross reference update batch job created.");  
 }  

martedì 5 febbraio 2019

D365FO - Gestione articoli in conto deposito

Gli items di proprietà del fornitore sono gestiti con una dimensione inventariale specifica “owner”.


Ogni valore della dimensione inventariale viene associata ad uno specifico fornitore.

La merce viene ricevuta in un magazzino D365 impiegando una tipologia di transazione chiamata “consignment replenishment order”. 

In fase di ricezione merce viene automaticamente assegnata la dimensione owner referenziata al codice fornitore, tale merce non ha valore di magazzino finchè non avviene il cambio di proprietà della merce stessa. 
Prima di impiegare la merce in produzione / vendita occorre cambiare la dimensione owner da valore fornitore a valore interno usando la transazione “inventory ownership change journal”. 

Al cambio di ownership della merce un ordine di acquisto viene generato automaticamente con riferimento a vendor/items coinvolti e nasce in stato “Ricevuto”, tale transazione sarà usata per la registrazione della fattura di acquisto. 
É possibile effettuare il conteggio della merce di proprietà del fornitore anche usando un comune giornale di counting.