hljs.configure({cssSelector: "code"}); hljs.highlightAll();

Tuesday, November 28, 2023

Upload image file in form grid using X++ in D365FO

  In this blog we are going to Learn how to add a simple button for users to upload signature images to specific records in a grid.

Form design:





Design details:

Data model changes:

1. Add container field with EDT as BitMap in table.

User interface changes:

Form design:

1. Added my table into form data source.

2. We cant directly add table container field into grid, so add new field in grid then map data source and field.


3.Added two Action menu items on action pane as "Change and  Remove".(for the best practice written code in classes, not in clicked methods, it reduces the client server side load.)

4. change button is for user to select image from local disc and Remove button is to remove the image file from record.

Class Architecture:

1. Created two classes for upload and remove.

Upload image: 

internal final class uploadImage
{
    public void uploadImageFile(formRun _fromRun)
    {
       FormRun         visualForm;
       FileUpload      fileUploadControl;
       str             imageFilePathName;

       visualForm = classFactory::formRunClassOnClient(new Args(formstr(SysGetFileFromUser)));
       visualForm.init();
       visualForm.design().caption("@ApplicationPlatform:GetFileImageCaption");
       fileUploadControl = visualForm.design().controlName('FileUpload1');
       visualForm.run();
       visualForm.wait();
       FileUploadTemporaryStorageResult fileUploadResult = fileUploadControl.getFileUploadResult();
       if (fileUploadResult != null && fileUploadResult.getUploadStatus())
       {
           imageFilePathName = fileUploadResult.getDownloadUrl();
       }
       if(imageFilePathName)
       {
	      formDataSource  signaturetable_dss =  _fromRun().dataSource(1) as FormDataSource;
	      Signaturetable     signaturetableLoc = signaturetable_dss.cursor() as Signaturetable;  
	      ttsbegin;
	      signaturetableLoc.selectForUpdate(true);
	      signaturetableLoc.Image =ImageReference::GetPackedBinaryData(imageFilePathName);
	      signaturetableLoc.update();
	      ttscommit;
       }
    }

    public static void main(Args _args)
    {
       FromRun formRun = _args.formRun();
       uploadImage object = new uploadImage();
       object.uploadImageFile(formRun);
    }
}

Remove image:

internal final class removeImage
{
    public static void main(Args _args)
    {
	FormRun formRun = _args.formrun();
	formDataSource  signaturetable_dss =  formRun().dataSource(1) as FormDataSource;
	Signaturetable     signaturetableLoc = signaturetable_dss.cursor() as Signaturetable;

	ttsbgein;
	signaturetableLoc.selectForUpdate(true);
	signaturetableLoc.Image = conNull();
	signaturetableLoc.update();
	ttscommit;
    }
}
Thanks for reading!!

Thursday, November 23, 2023

How to create OData action in D365FO and use it in Logic Apps

OData Action in D365FO and Using it in logic apps


Step 1: Create a OData action method in data entity as below and compile the code to see the method in logic app or postman.

With input parameter:
[SysODataActionAttribute("SalesOrderConfirm", false)] --If your method is Static then it should be false else True
public static void salesOrderConfirm(str    _salesId, DataAreaId _company)
{
    SalesTable      salesTable;
    SalesFormLetter salesFormLetter;

    salesTable = SalesTable::find(_salesId);

    salesFormLetter = SalesFormLetter::construct(DocumentStatus::Confirmation);
    salesFormLetter.update(salesTable, DateTimeUtil::getSystemDate(DateTimeUtil::getUserPreferredTimeZone()), SalesUpdate::All);
}

With List as Response:
[SysODataActionAttribute("GetSalesOrders", False),
SysODataCollectionAttribute("return", Types::Record, "SalesOrder")]
public static List GetSalesOrders(str custAccount, DataAreaId company)
{
  List returnList = new List(Types::Record);

  changecompany(company)
  {
     SalesTable  SalesTable;

     while select SalesTable
	where SalesTable.CustAccount == custAccount
    {
	returnList.addend(SalesTable);
    }
  }
  return returnList;
}

With input and ouput:
[SysODataActionAttribute("GetSalesStatus", False)]

public static str getStatus(str    salesId, DataAreaId company)
{
   str  status;
   changecompany (company)
   {
      status = enum2Str(SalesTable::find(salesId).SalesStatus);
   }
   return  status;
}

With Instance method(Action atrribute to True):

[SysODataActionAttribute("GetLineCount", true)]
public int GetLineCount()
{
  int  lineCount;
  changecompany (this.DataAreaID)
  {
     Salesline  salesline;
  
     select count(RecId) from salesline
  	where salesline.SalesId== this.SalesId;
  
     lineCount = salesline.RecId;
  }
  return  lineCount;
}

Step 2: Create a logic app to trigger the odata action method

                1. Search HTTP  and select When http request is received.
                2. Prepare JSON format your input values in my case Sales id, DataareaId.
                        {
                            "SalesId":"",
                            "Company":""
                        }
                3. Search fin & apps and select Execute action trigger.
      •   Specify Instance,
      •   Action(Your method Name. In this case SalesOrderConfirm) 
      •  Set input parameters(SalesId, Company).

Logic App Flow:
 


Test the flow with LA and Postman:


 Create POST request in Postman :

  •     For Non instance method output and URL
                POST: https://D365URL/data/MyEntity/Microsoft.Dynamics.DataEntities.SalesOrderConfirm


  •    Instance method output and URL
                 POST:
 D365URL/data/SalesOrder(dataAreaId='USMF',SalesId='1235')/Microsoft.Dynamics.DataEntities.GetLineCount




Thanks for reading!!

Thursday, October 26, 2023

Recurring integration in D365FO

Recurring integration in D365FO(Enqueue).

Requirement : To get the files from blob and upload it in DMF Entities.

To achieve this we are using Recurring integration(Enqueue) using logic apps.

Step 1:

        Create DMF import project in Data management and open manage recurring data jobs.

Step 2:

        Create recurring data job.

            1. Click new, Provide name and description.

            2. Enter application ID and Make Enabled column as Yes.

            3. Set the recurrence.

            4. Enable Is recurring Data job enabled?

            5. Copy the Activity Id.


 Step 3:

    Creating logic app.

        Find below logic app flow.



        1. Setting up the recurrence.
        2. Establishing the blob connection and getting file from specific blob folder.
        3. Creating HTTP web request with POST method.
             Method : Post
             Url :https://<base URL>/api/connector/enqueue/<activity ID>?entity=<entity label name>
             Body : From Step 2.
             Authentication: Input your authentication details(client, secret,scope and tenant Id)
   

        4. Initially when it gets triggered it will be in Queued state.  So added Delay trigger to get the d365             execution status.
        5. To get the Execution status, triggered the HTTP web request.
            Method: Post
            Url D365 Url/data/DataManagementDefinitionGroups/Microsoft.Dynamics.DataEntities.GetMessageStatus
              Body :
                    {
                            "messageId":"<string>"
                    }
               Authentication: Input your authentication details(client, secret,scope and tenant Id).


    Run the logic app:
                                        


    


Reference: 

 Thanks for reading!!

Monday, September 25, 2023

Download multiple attachments of sales order in one click(Zip file) Using X++ in D365FO

I have a requirement to download Sales Order attachments and send a download link to the user in One click. If there are multiple attachments available, the system will download the last attachment if we try to download file by file. To achieve this, we create a ZIP file for all the attachments and send a download link to the user.


public static void main(Args _args)
{
   System.IO.Stream fileStream;
   System.IO.MemoryStream zipArchiveStream = new System.IO.MemoryStream();
   DocuRef docuRef;
   DocuValue docuValue;
   SalesTable  sales = SalesTable::find('000811');

   using(System.IO.MemoryStream zipStream  = new System.IO.MemoryStream())
   {
     using(System.IO.Compression.ZipArchive archive  = new System.IO.Compression.ZipArchive(zipStream, System.IO.Compression.ZipArchiveMode::Create, true))
     {
	  while select docuRef
	     where docuRef.RefRecId == sales.RecId
	  {
	     select docuValue where docuValue.RecId == docuRef.ValueRecId;
	     System.IO.Compression.ZipArchiveEntry dataFileEntry   = archive.CreateEntry(docuValue.filename());

	     using (System.IO.Stream dataFileEntryStream = dataFileEntry.Open())
	     {
		System.IO.Stream stream =DocumentManagement::getAttachmentStream(docuRef);
		stream.CopyTo(dataFileEntryStream);
	     }
	  }
     }
		
	File::SendFileToUser(zipStream, "Test" + '.zip');
  }
}
   

Output:


Thanks for reading!!

Thursday, August 31, 2023

Legal entity multiselect lookup in D365FO X++

 Legal entity multiselect lookup in D365FO X++ 

public void lookup()
{
   // super();
   Query       query               = new Query();
   TableId     multiSelectTableNum = tableNum(DataArea);
   container   selectedFields = [multiSelectTableNum, fieldName2id(multiSelectTableNum, fieldStr(DataArea, id))];
   query.addDataSource(tableNum(DataArea));
   query.dataSourceTable(tableNum(DataArea)).addSelectionField(fieldNum(DataArea,id));
   query.dataSourceTable(tableNum(DataArea)).addSelectionField(fieldNum(DataArea,Name));
   SysLookupMultiSelectGrid::lookup(query, this,this,this,selectedFields);
}
Query       query               = new Query();
TableId     multiSelectTableNum = tableNum(DataArea);
container   selectedFields = [multiSelectTableNum, fieldName2id(multiSelectTableNum, fieldStr(DataArea, id))];
query.addDataSource(tableNum(DataArea));
query.dataSourceTable(tableNum(DataArea)).addSelectionField(fieldNum(DataArea,id));
query.dataSourceTable(tableNum(DataArea)).addSelectionField(fieldNum(DataArea,Name));

ctrl = SysLookupMultiSelectCtrl::constructWithQuery(this.formRun(),
								this,
								query,
								false,
								selectedFields);

container con = ctrl.GetselectedFieldValues();


Thanks for reading!!

Friday, August 25, 2023

X++ code to get Derived dimension

 X++ code to get Derived dimension

public static str derivedDim(str _dimAttrName,str _dimValue)
{
    //_dimAttrName   Base dimension
    // _dimValue     Base dimension value
    DimensionAttributeValueDerivedDimensions derivedDim;
    DimensionAttribute          dimAttribute;
    DimensionAttributeValue     dimAttriValue;
    str                         derivedDimValue;
    dimAttribute = DimensionAttribute::findByName(_dimAttrName);
    if(dimAttribue)
    {
	dimAttriValue   = DimensionAttributeValue::findByDimensionAttributeAndValue(dimAttribute,_dimValue);
	derivedDim      = DimensionAttributeValueDerivedDimensions::findByDimensionAttributeValue(dimAttribute.RecId,dimAttriValue.RecId);
	dimAttriValue   = DimensionAttributeValue::find(derivedDim.DerivedDimensionAttributeValue1);

	select firstonly derivedDim where derivedDim.DimensionAttribute == dimAttribute.RecId;
	dimAttribute = DimensionAttribute::find(derivedDim.DimensionAttribute);

	if(dimAttribute)
	{
	   derivedDimValue = dimAttriValue.DisplayValue;
	}
	
    }
    return derivedDimValue;
}

Thanks for reading!!

Thursday, August 24, 2023

Financial dimension lookup

 

Financial dimension lookup Code X++.

public static void lookupControl(FormControl _formControl, str _value)
{
	FormStringControl   control;
	Args                args;
	FormRun             formRun;
	DimensionAttribute  dimAttribute;
	dimAttribute    =   DimensionAttribute::findByName(_value);

	args            =   new Args();
	args.record(dimAttribute);
	args.caller(_formControl);
	args.name(formStr(DimensionLookup));

	formRun         =   classFactory.formRunClass(args);
	formRun.init();
	control         =   _formControl as FormStringControl;
	control.performFormLookup(formRun);
}

Friday, August 4, 2023

Inclusion of Bank Details During Copying Vendor From one LE to Other

Standard functionality allows for copying vendors from one legal entity (LE) to another, but it doesn't include the transfer of bank details. To enhance this, we have customized the vendor copying process to seamlessly incorporate the bank details functionality.

Please find the below code for the same.

[ExtensionOf(classStr(CustVendCopyDataUtil))]
final static class CustVendCopyDataUtil_Extension
{
   //Copy of bank details from one LE to other when copying the vendors 
    protected static void copyVendFormDataSources(VendTable sourceVendor, FormRun vendTableForm, Set datasourceSet, boolean needCreate)
    {
        FormDataSource  vendTable_ds;
        VendTable       vendTableLoc;
        next copyVendFormDataSources(_sourceVendor,_vendTableForm,_datasourceSet,_needCreate);
        vendTable_ds = _vendTableForm.dataSource(formDataSourceStr(VendTable, VendTable)) as FormDataSource;
        vendTableLoc = vendTable_ds.cursor();
        CustVendCopyDataUtil::CopyBankDetailsofVendor(_sourceVendor,vendTableLoc.AccountNum);
    }

    protected static void copyVendTableForVendor(VendTable sourceVendor, FormRun vendTableForm, Set datasourceSet)
    {
        FormDataSource  vendTable_ds;
        VendTable       vendTableLoc;
        next copyVendTableForVendor(_sourceVendor,_vendTableForm,datasourceSet);
        vendTable_ds = _vendTableForm.dataSource(formDataSourceStr(VendTable, VendTable)) as FormDataSource;
        vendTableLoc = vendTable_ds.cursor();
        buf2Buf(_sourceVendor, vendTableLoc);
        vendTable_ds.cursor().data(vendTableLoc);
        datasourceSet.add(vendTable_ds);
    }

    static void CopyBankDetailsofVendor(VendTable vendorTable,VendAccount accountNum)
    {
        VendTable   vendtable;
        VendBankAccount vendBankAccount, vendBankAccountLoc;
        while select  crosscompany * from vendBankAccountLoc
            where vendBankAccountLoc.VendAccount == _vendorTable.AccountNum
                && vendBankAccountLoc.DataAreaId == _vendorTable.DataAreaId
        {
            select vendBankAccount
                where vendBankAccount.VendAccount == vendBankAccountLoc.VendAccount
                    && vendBankAccount.AccountID == vendBankAccountLoc.AccountID;

            if (!vendBankAccount.VendAccount)
            {
                ttsbegin;
                buf2Buf(vendBankAccountLoc, vendBankAccount);
                vendBankAccount.VendAccount = _accountNum;
                vendBankAccount.insert();
                ttscommit;
            }
        }
    }
}

Thanks for reading my blog.

Keep Learning!!

Tables involved while opening the voucher transactions from purchase order receipts

 

 1. GeneralJournalAccountEntry

  2. GeneralJournalEntry

  3. SubledgerVoucherGeneralJournalEntry

  4. VendPackingSlipVersion

  5. VendPackingSlipJour

select generalJournalAccountEntry
join SubledgerVoucher,AccountingDate from generalJournalEntry          
where generalJournalEntry.RecId == generalJournalAccountEntry.GeneralJournalEntry
	&& generalJournalEntry.DocumentNumber == _VendPackingSlipJour.packingSlipId
	&& generalJournalEntry.AccountingDate == _VendPackingSlipJour.DeliveryDate
	&& generalJournalEntry.SubledgerVoucherDataAreaId == _VendPackingSlipJour.dataAreaId
exists join subledgerVoucherGeneralJournalEntry
   where subledgerVoucherGeneralJournalEntry.GeneralJournalEntry == generalJournalEntry.RecId
exists join VendPackingSlipVersion 
   where VendPackingSlipVersion.AccountingDate == subledgerVoucherGeneralJournalEntry.AccountingDate
  && VendPackingSlipVersion.LedgerVoucher == subledgerVoucherGeneralJournalEntry.Voucher
    && VendPackingSlipVersion.VendPackingSlipJour == VendPackingSlipJour.Recid;
		


Keep Learning!!


Friday, July 28, 2023

X++ to get addresses,GST Numbers,state code,HSN/SAC codes from Tax information of customer invoice journal in D365FO

 X++ to get addresses,GST Numbers,state code,HSN/SAC codes from Tax information of customer invoice journal in D365FO

TaxDocumentRowTransaction           taxDocumentRowTransaction;
TaxDocumentRowTransaction_IN        taxDocumentRowTransaction_IN;
TransTaxInformationHelper           transTaxInformationHelper;
TransTaxInformation                 transTaxInformation, transTaxInformationItem;
LogisticsPostalAddress              logisticsPostalAddress, supplierPostalAddress;
LogisticsAddressState               logisticsAddressState, supplierState;
CustInvoiceTrans					CustInvoiceTrans;
		
select taxDocumentRowTransaction
	where taxDocumentRowTransaction.voucher  == ""
		&& taxDocumentRowTransaction.Source == TaxModuleType::SalesInvoice
	join taxDocumentRowTransaction_IN
	where taxDocumentRowTransaction_IN.TaxDocumentRowTransactionRecId == taxDocumentRowTransaction.RecId;

//Retrieving address of a sales invoice journal(Customer tax information address from transaction level)
	
logisticsPostalAddress = LogisticsPostalAddress::findRecId(taxDocumentRowTransaction.PartyPostalAddress);
logisticsAddressState = LogisticsAddressState::find(logisticsPostalAddress.CountryRegionId, logisticsPostalAddress.State);

//Retrieving address of a sales invoice journal (company address from the transaction level)

supplierPostalAddress  = LogisticsPostalAddress::findRecId(taxDocumentRowTransaction.CompanyPostalAddress);
supplierState          = LogisticsAddressState::find(supplierPostalAddress.CountryRegionId, supplierPostalAddress.State);

select firstonly CustInvoiceTrans
	where CustInvoiceTrans.ParentRecId == custInvoiceJour.RecId;
	
// To get all the details from Tax Information at a Transaction level
transTaxInformationHelper = TransTaxInformationHelper::newHelper();
transTaxInformation       = transTaxInformationHelper.getTransTaxInformation(tableNum(CustInvoiceTrans), custInvoiceTransLoc.RecId);

// To get the Company address from tax information.
LogisticsLocationEntity::location2PostalAddress(transTaxInformation.CompanyLocation, DateTimeUtil::getSystemDateTime(), true).Address;
LogisticsLocationEntity::location2PostalAddress(transTaxInformation.CustomerLocation, DateTimeUtil::getSystemDateTime(), true).City;
LogisticsLocationEntity::location2PostalAddress(transTaxInformation.CustomerLocation, DateTimeUtil::getSystemDateTime(), true).ZipCode;

// Customer tax information address
LogisticsLocationEntity::location2PostalAddress(transTaxInformation.CustomerLocation, DateTimeUtil::getSystemDateTime(), true).Address;

// To Find HSN/SAC Code
HSNCodeTable_IN::find(transTaxInformation.HSNCodeTable).Code;
ServiceAccountingCodeTable_IN::find(transTaxInformation.ServiceAccountingCodeTable).SAC;

//Warehouse address
inventLocationLogisticsLocation inventLocationLogisticsLocation;
logisticsLocation 				logisticsLocation;
LogisticsPostalAddress  		logisticsPostalAddress;

InventLocation inventFromLocation = InventLocation::find('warehouse');
Select firstOnly inventLocationLogisticsLocation
	where inventLocationLogisticsLocation.InventLocation == inventFromLocation.RecId
		&& inventLocationLogisticsLocation.IsPrimary == 1;
		
if (inventLocationLogisticsLocation)
{
	select firstOnly logisticsLocation
		where logisticsLocation.RecId == InventLocationLogisticsLocation.Location;
}
logisticsPostalAddress = logisticsPostalAddress::findByLocation(logisticsLocation.recid);
info(LogisticsPostalAddress.Address);

// Replace '\n' with null in address
TextBuffer textBuffer = new TextBuffer();
textBuffer.setText(Address);
textBuffer.Replace('\n','');

info(textBuffer.getText());

//Get GSTIn Numbers
TaxRegistrationNumbers_IN taxRegistrationNumbers_IN;
TaxInformation_IN         taxInformation_IN;

select taxRegistrationNumbers_IN
	join taxInformation_IN
	where taxInformation_IN.GSTIN == taxRegistrationNumbers_IN.RecId
		&& taxInformation_IN.RecID == transTaxInformation.CustomerTaxInformation;
		
Info(taxRegistrationNumbers_IN.RegistrationNumber);

//Company GSTIN

TaxRegistrationNumbers_IN::find(transTaxInformation.GSTIN).RegistrationNumber;

Keep Learning!!

Tuesday, July 18, 2023

Custom service in D365FO

 Recently, I've got an requirement to send the vendor payments from D365FO to third party system. For that I have created custom service and exposed it to external application.

Steps to create custom service.

Step 1. Create a contract class for request.

Whenever third party system accessing our custom service they will provide invoice number as input. Based on the input will be sending the payment data against it.
Class CSVendTransRequestContract
{
    private   InvoiceID         InvoiceId;
    
    [DataMember("Invoice number")]
    public InvoiceId parmInvoiceNumber(InvoiceId _value = InvoiceId)
    {
        InvoiceId = _value;
        return InvoiceId;
    }
}

Step 2. Create another contract class for Response.

Response for our CS would be below columns form response contract class.



[DataContractAttribute,Newtonsoft.Json.JsonObject(IsReference = false)]
class CSVendTransResponseContract
{
    private   Voucher         paymentVoucher;
    private   Date            Pdate;
    private   Amount          amount,tdsAmount;
    private   str             number;
    private   str             paymentReference;

    [DataMember("Payment voucher")]
    public Voucher parmPaymentVoucher(Voucher _value = paymentVoucher)
    {
        paymentVoucher = _value;
        return paymentVoucher;
    }
    [DataMember("Date")]
    public date parmDate(date _value = Pdate)
    {
        Pdate = _value;

        return Pdate;
    }
    [DataMember("Amount")]
    public Amount parmAmount(Amount _value = amount)
    {
        amount = _value;
        return Amount;
    }
    [DataMember("TDS Amount")]
    public Amount parmTDSAmount(Amount _value = tdsAmount)
    {
        tdsAmount = _value;
        return tdsAmount;
    }
    [DataMember("Payment reference")]
    public Name parmPaymentReference(Name _value = paymentReference)
    {
        paymentReference = _value;
        return paymentReference;
    }
}

Step 3: Create a service class to run the business logic and return the response.

class CSVendTransService
{
    [AifCollectionType('return', Types::Class, classStr(CSVendTransResponseContract))]
    public  List SendResponse(CSVendTransRequestContract _invoices)
{ VendTrans vendTrans; VendSettlement vendSettlement; List resultSet = new List(Types::Class); while select SettleAmountMST,OffsetTransVoucher,TransDate from vendSettlement join PaymReference from vendTrans where vendTrans.recid == vendSettlement.TransRecId && vendTrans.AccountNum == vendSettlement.AccountNum && vendTrans.DataAreaId == vendSettlement.TransCompany && vendTrans.Invoice == _invoices.
parmInvoiceNumber( && vendTrans.TransType == LedgerTransType::Purch { CSVendTransResponseContract responseContract = new CSVendTransResponseContract(); responseContract.parmPaymentVoucher(vendSettlement.OffsetTransVoucher); responseContract.parmAmount(vendSettlement.SettleAmountMST); responseContract.parmDate(vendSettlement.TransDate); responseContract.parmTDSAmount(VendTrans_W::findByVendTrans(vendTrans.RecId).TDSAmount_IN); responseContract.parmPaymentReference(LedgerJournalTrans::find(vendtransLoc.JournalNum,vendtransLoc.Voucher,false).PaymReference); resultSet.addEnd(responseContract); } } return resultSet; }


Step 4: Create a service and open its properties and assign the service class Name (CSVendTransService). Refer below image





Next step is to create service operation and give a name and assign method name(SendResponse) in properties as below image.





Step 5 : Create a Service Group 
Right click on service group, create service and in its properties select the service created in step 4.

Refer below image.



Custom service is completed. Now lets test this custom service in Postman.

URL :

 https://xxxxx.sandbox.operations.dynamics.com/api/services/service group name/service name/service operation name

Sample request JSON

{
	_invoices :
	{
		"Invoice number" :"Test"
	}
}
Note : If you want to send the list of invoices in request body. in request contract create parm method which returns list.
 We have to create new contract class and parm method which returns list.

Sample JSON for List of invoices
{
	_invoices : [
	        {
		        "Invoice number" :"Test"
	        }
        ]
}
[DataContractAttribute] 
public class CSVendTransInvoicesListRequest
{
    private List    Invoices;

    [DataMemberAttribute('Contract'),
    DataCollection(Types::Class, classStr(CSVendTransResponseContract)),
    AifCollectionTypeAttribute('_contractClass', Types::Class, classStr(CSVendTransResponseContract)),
    AifCollectionTypeAttribute('return', Types::Class, classStr(CSVendTransResponseContract))]
    public List parmInvoices(List _contractClass = Invoices)
    {
        Invoices = _contractClass;
        return Invoices;
    }
}

Retrieving the list values in service class method.
class CSVendTransService
{
    [AifCollectionType('return', Types::Class, classStr(CSVendTransResponseContract))]
    public  List SendResponse(CSVendTransInvoicesListRequest _invoices)
    {
		list invoices  = _invoices.Parminvoices();
		listEnumerator enumerator = invoices.getEnumerator();
		while(enumerator.MoveNext())
		{
			CSVendTransRequestContract  contract = new CSVendTransRequestContract();
			contract = enumerator.current();
			
			// DO your operation
		}
	}
}

Keep Learning!!

Wednesday, July 5, 2023

Export DMF Package Using logic apps

Export DMF Package Using logic apps

Step 1: Create a  Http request is received and Build the request body with parameters.

Definition Group Id and Package name  is Data Project Name.


Step 2: Create a new step and select Fin & Ops trigger as below image. After that select the environment and make the connection.

Step 3: Create a Until Trigger and within until trigger create Delay trigger and Fin and Ops Execute action trigger.

Fin & Ops DMF action is DataManagementDefinitionGroups-GetExecutionSummaryStatus.

In until Trigger write below expression.

@or(equals(body('Execute_action_3')?['value'], 'Succeeded'),equals(body('Execute_action_3')?['value'], 'PartiallySucceeded'),equals(body('Execute_action_3')?['value'], 'Failed')).

This step returns the execution Id.

Step 4: Create a fin & Ops execution action trigger and input for this step is Execution id from the previous step.

The DMF Action is 

DataManagementDefinitionGroups-GetExportedFileUrl which returns the file url that is exported in FO.

Step 5: Create a HTTP GET and Response triggers.

HTTP GET >> Input is File URL from Previous step and response is content of the file.



You can add additional triggers to save this file in Blob or SFTP.

Final Flow:



Keep Learning!!



Tuesday, July 4, 2023

To open a particular report in a specific language in D365FO Using X++

To open a particular report in a specific language in D365FO, you will need to create the labels for that language. The labels contain the translated text for different elements of the report, such as field labels, column headers, and other user interface elements.  

By parameterizing the language ID and maintaining it in the report contract, you can dynamically set the language at runtime and generate the report in the desired language without hardcoding the value.

In my case it is Hardcoded.

Write below code in controller class Main method.

  public static void main(Args _args)
  {
   LanguageSpecificContract    contract;
   LanguagespecificController  controller;       	
   controller = new LanguagespecificController();
   controller.parmReportName(ssrsReportStr(LanguageSpecificReport, Report));       
    controller.parmReportContract().parmRdlContract().parmLabelLanguageId("fr-FR");// To open a report in specific language.
   contract = controller.parmReportContract().parmRdpContract() as LanguageSpecificContract;
controller.parmArgs(_args); controller.parmShowDialog(false); controller.startOperation(); }

Keep Learning!!

Monday, June 19, 2023

Form level event handlers in D365FO

 

1. OnActivated.
//The primary purpose of the _OnActivated event handler is to handle specific actions or modifications
// that need to occur when a form or control becomes active. This can include tasks such as refreshing data,
 [FormEventHandler(formStr(PurchEditLines), FormEventType::Activated)] 
 public static void PurchEditLines_OnActivated(xFormRun sender, FormEventArgs e)
 { 
	 FormRun 		formrun = sender; //  form  formrun. 
	 FormDataSource formdatasource = formrun.dataSource();      // formdatasource   
	 PurchParmTable purchParmTable = formdatasource.cursor(); // active record.    
 }

2. OnClosing

// onclosing: the onclosing eventhanlders is triggered while closing the form.
[FormEventHandler(formStr(PurchEditLines), FormEventType::Closing)]
public static void PurchEditLines_OnClosing(xFormRun sender, FormEventArgs e)
{        
	FormRun         formrun = sender; //  form  formrun.
	FormDataSource  formdatasource = formrun.dataSource(); // formdatasource 
	PurchParmTable  purchParmTable = formdatasource.cursor();		
	formdatasource.reread(); 
	formdatasource.refresh();
	formdatasource.research();
}

3.OnInitialized.
//OnInitialized: The OnInitialized event handler is triggered after the form has been fully initialized 
//and is ready to be interacted with by the user. At this stage, all form controls have been rendered,
// and data sources and data bindings have been established. The OnInitialized event provides an opportunity to perform additional actions, 
//such as populating default values, setting focus on specific controls, or applying any post-initialization logic.

[FormEventHandler(formStr(PurchEditLines), FormEventType::Initialized)]
public static void PurchEditLines_OnInitialized(xFormRun sender, FormEventArgs e)
{        
	 FormRun         formrun = sender; //  form  formrun.
	 formrun         senderformrun = formrun.caller_RU();
	 PurchTable      purchtable = formrun.args().record(); // calling form active record using args.
}

4. OnInitializing

//OnInitializing: The OnInitializing event handler is triggered before the form is fully initialized
// and displayed to the user. It is used to perform any necessary setup or modifications on the form
// and its elements before they are rendered on the screen. This event allows you to customize the initial state of the form and its controls.


 [FormEventHandler(formStr(PurchEditLines), FormEventType::Initializing)]
 public static void PurchEditLines_OnInitializing(xFormRun sender, FormEventArgs e)
 {        
	FormRun                 formrun = sender;
	FormButtonControl       control = formrun.design().controlName('control name'); 
	control.visible(false); 
 }

5.OnPostRun

/// If you want to jump from FormControl, to another Form you can 
[FormEventHandler(formStr(PurchEditLines), FormEventType::PostRun)] 
public static void PurchEditLines_OnPostRun(xFormRun sender, FormEventArgs e)
{   
FormStringControl   control = sender.design().controlName(formControlStr(purcheditlines, ok));
control.registerOverrideMethod(methodStr(FormStringControl, jumpRef), methodStr(your jumrefclass, jumpRef), sender);
}


    -----------------------------------------------------------------------------------------------------------------------------------
6. OnActivated
// this is a datasource active method.

 [FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmUpdate), FormDataSourceEventType::Activated)]
 public static void PurchParmUpdate_OnActivated(FormDataSource sender, FormDataSourceEventArgs e)
 { 
	FormDataSource  formdatasource = sender; // current form datasource. 
	FormRun         formrun = formdatasource.formRun(); // current fromrun 
	FormControl     controlname = formrun.design().controlName('name of the control'); // to get the formcontrl.
	PurchParmTable purchparmtable = formdatasource.cursor(); // active record.
	if (purchparmtable.RecId)
	{    
	controlname.visible(true); 
	} 
 }

7.OnCreated
// The primary purpose of the OnCreated event handler is to handle specific actions or modifications that need
// to occur when a new record or object is created. This can include tasks such as initializing default values, 
//performing calculations, setting up relationships or dependencies, or any other behavior that should be triggered 
//when a new instance is created.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::Created)]
public static void PurchParmTable_OnCreated(FormDataSource sender, FormDataSourceEventArgs e)
{ 
	FormDataSource  formdatasource = sender; // current form datasource.
	FormRun         formrun = formdatasource.formRun(); // current fromrun 
	PurchParmTable purchparmtable = formdatasource.cursor(); // active record.
	purchparmtable.DeliveryName = "default value";  // initilizing some default values to current record  
}

8.OnCreating

1.Allows customization of the record's initial state before it is saved.
2. Useful for performing validations, setting default values, or modifying the record before it is created.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable),   FormDataSourceEventType::Creating)]
public static void PurchParmTable_OnCreating(FormDataSource sender, FormDataSourceEventArgs e)
{
   FormDataSource  formdatasource = sender; // current form datasource.
   PurchParmTable purchparmtable = formdatasource.cursor(); // active record.
   purchparmtable.validations();
}

9.OnDeleted
//The primary purpose of the OnDeleted event handler is to handle specific actions or modifications
// that need to occur when a record or object is deleted. This can include tasks such as updating related records,
// performing clean-up operations, logging deletion details, or any other behavior that should be triggered when a deletion occurs.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::Deleted)]
public static void PurchParmTable_OnDeleted(FormDataSource sender, FormDataSourceEventArgs e)
{    
	FormDataSource  formdatasource = sender; // current form datasource. 
	PurchParmTable purchparmtable = formdatasource.cursor(); // active record  
	if (purchparmtable.PurchId) 
	{        
	 warning("since purchi id is available you cant delete the record");  
	}  
}


10.OnDisplayOptionInitialize
//The primary purpose of the OnDisplayOptionInitialize event handler is to handle specific actions
// or modifications related to the display options of form controls. This can include tasks such as enabling 
//or disabling controls, setting their visibility, modifying their labels, or any other behavior
// that should be applied during the initialization of the form.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::DisplayOptionInitialize)]
public static void PurchParmTable_OnDisplayOptionInitialize(FormDataSource sender, FormDataSourceEventArgs e)
{   
	FormDataSource  formdatasource = sender; // current form datasource. 
	FormRun         formrun = formdatasource.formRun(); // current fromrun 
	PurchParmTable purchparmtable = formdatasource.cursor(); // active record 
	FormControl     controlname = formrun.design().controlName('name of the control'); // to get the formcontrl. 
	if (purchparmtable.PurchId) 
	{ 
	   controlname.allowEdit(true); 
	}  
}
	
11.OnInitialized
 // The OnInitilize event handler at the datasource level is particularly useful
 //when you need to customize or set up the initial state of the datasource and its related elements, 
 //such as fields or relations. It allows you to perform actions that should occur during the initialization phase of the datasource,
 //ensuring that the datasource is prepared and ready for data operations.
 
[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::Initialized)]
public static void PurchParmTable_OnInitialized(FormDataSource sender, FormDataSourceEventArgs e)
{ 
	 FormDataSource  formdatasource = sender; // current form datasource. 
	 FormRun         formrun = formdatasource.formRun(); // current fromrun 
	 PurchParmTable purchparmtable = formdatasource.cursor(); // active record.
	 purchparmtable.PurchName =""; 
}

12.OnInitValue
//The primary purpose of the OnInitValue event handler at the datasource level is to handle
// specific actions or modifications that need to occur when a field's value is being initialized. 
//This can include tasks such as setting default values, performing calculations based on other field values,
// or any other behavior that should be triggered during the value initialization process.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::InitValue)]
public static void PurchParmTable_OnInitValue(FormDataSource sender, FormDataSourceEventArgs e)
{  
  FormDataSource  formdatasource = sender; // current form datasource.
  FormRun         formrun = formdatasource.formRun(); // current fromrun
  PurchParmTable purchparmtable = formdatasource.cursor(); // active record.
  purchparmtable.PurchName ="";
}
  
13.OnLeavingRecord
//The primary purpose of the OnLeavingRecord event handler at the datasource level is to handle specific actions
// or modifications that need to occur when the focus is leaving a record. This can include tasks such as validating
// the record's data, performing calculations or updates based on the record's values, or any other behavior that should be 
//triggered before moving to another record.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::LeavingRecord)]
public static void PurchParmTable_OnLeavingRecord(FormDataSource sender, FormDataSourceEventArgs e)
{ 
	FormDataSource  formdatasource = sender; // current form datasource. 
	FormRun         formrun = formdatasource.formRun(); // current fromrun
	PurchParmTable purchparmtable = formdatasource.cursor(); // active record.   
	if (purchparmtable.PurchName =="") 
	{  
		warning("purch name should be enetered");
	}  
}
	
14.OnLeftRecord
// The OnLeftRecord event handler at the datasource level is used to perform actions or operations
// after the focus has moved away from a record within a datasource. It is triggered after the focus has left
// the current record, allowing you to perform any necessary calculations, updates, or other actions related to the record

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::LeftRecord)]
 public static void PurchParmTable_OnLeftRecord(FormDataSource sender, FormDataSourceEventArgs e)
{        
	 FormDataSource  formdatasource = sender; // current form datasource.
	 FormRun         formrun = formdatasource.formRun(); // current fromrun 
	 PurchParmTable  purchparmtable = formdatasource.cursor(); // active record. 
	 select forupdate TableName      
	 where    TableName.fieldname == purchparmtable.fieldname;
	 
	 ttsbegin;
	 TableName.fieldname == purchparmtable.fieldname;
	 TableName.update();
	 ttscommit;
}
	
15.MarkChanged
//The primary purpose of the OnMarkChanged event handler at the datasource level is to handle specific
// actions or modifications that need to occur when a field's value has been changed. This can include
// tasks such as performing calculations, updating related fields or records, or any other behavior that
// should be triggered when a field is marked as changed.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::MarkChanged)]
public static void PurchParmTable_OnMarkChanged(FormDataSource sender, FormDataSourceEventArgs e)
{  
	FormDataSource  formdatasource = sender; // current form datasource. 
	FormRun         formrun = formdatasource.formRun(); // current fromrun 
	PurchParmTable  purchparmtable = formdatasource.cursor(); // active record.
	
	formdatasource.object(fieldNum(PurchParmTable,PurchName)); 
	 
} 

16.OnPostLinkActive
//The OnPostLinkActive event handler is called when a link is activated on a form data source.
// It is used to perform some action after the link has been activated. The event handler is defined in the FormDataSource class
// and can be overridden in a subclass. The event handler has the following

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::PostLinkActive)]
public static void PurchParmTable_OnPostLinkActive(FormDataSource sender, FormDataSourceEventArgs e)
{      
	FormDataSource  formdatasource = sender; // current form datasource.
	FormRun         formrun = formdatasource.formRun(); // current fromrun
	PurchParmTable  purchparmtable = formdatasource.cursor(); // active record. 
}
	
17.OnQueryExecuted

//The primary purpose of the OnQueryExecuted event handler at the datasource level is to handle specific actions or modifications
// that need to occur after a query has been executed. This can include tasks such as manipulating the result set,
// performing calculations or aggregations on the retrieved data, or any other behavior that should be triggered after the query execution.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::QueryExecuted)] 
public static void PurchParmTable_OnQueryExecuted(FormDataSource sender, FormDataSourceEventArgs e)
{       
	sender.query().dataSourceName(sender.name()).addRange(fieldnum(purchparmtable,     purchname)).value("");  
}

18.OnQueryExecuting

//The primary purpose of the OnQueryExecuting event handler at the datasource level is to handle specific actions
// or modifications that need to occur before a query is executed. This can include tasks such as modifying the query,
// applying filters, or any other behavior that should be triggered before the query is sent to the data source.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::QueryExecuted)] 
public static void PurchParmTable_OnQueryExecuting(FormDataSource sender, FormDataSourceEventArgs e)
{       
	sender.query().dataSourceName(sender.name()).addRange(fieldnum(purchparmtable,     purchname)).value("");  
}

19.OnRefreshed
//The primary purpose of the OnRefreshed event handler at the datasource level is to handle specific actions or modifications 
//that need to occur after the datasource has been refreshed and all data-related events have been processed.
// This can include tasks such as updating dependent controls or calculations based on the refreshed data,
// or any other behavior that should be triggered after the data has been refreshed and all data-related events have been handled.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::Refreshed)]
public static void PurchParmTable_OnRefreshed(FormDataSource sender, FormDataSourceEventArgs e) 
{   
	FormDataSource  formdatasource = sender; // current form datasource.
	FormRun         formrun = formdatasource.formRun(); // current fromrun.
	PurchParmTable  purchparmtable = formdatasource.cursor(); // active record. 
	controlname mycontrol = formrun.design().controlName('');
	if (purchparmtable)
	{
		mycontrol.enable(true);
	}
}

20.OnReread.

//The primary purpose of the OnReread event handler at the datasource level is to handle specific actions
// or modifications that need to occur after the datasource has been reloaded with the same set of data.
// This can include tasks such as refreshing dependent controls, recalculating values, or any other behavior
// that should be triggered after the data has been reloaded.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::Reread)]
public static void PurchParmTable_OnReread(FormDataSource sender, FormDataSourceEventArgs e)
{  

}
	 
21.OnSelectionChanged.

//The primary purpose of the OnSelectionChanged event handler at the datasource level is to handle specific actions
// or modifications that need to occur when the selection of records in the datasource is changed.
//This can include tasks such as updating dependent controls, recalculating values, or any other behavior
// that should be triggered when the user changes the selected records.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::SelectionChanged)]
public static void PurchParmTable_OnSelectionChanged(FormDataSource sender, FormDataSourceEventArgs e)
{   

}    

22.OnValidatedDelete

 [FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::ValidatedDelete)]
 public static void PurchParmTable_OnValidatedDelete(FormDataSource sender, FormDataSourceEventArgs e)
 {  
 }
 
23. OnValidatedWrite

 [FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::ValidatedWrite)]
 public static void PurchParmTable_OnValidatedWrite(FormDataSource sender, FormDataSourceEventArgs e)  
 {  
 }
 
 24.OnWriting.
 
 //The primary purpose of the OnWriting event handler at the datasource level is to handle specific actions
// or modifications that need to occur before a write operation is performed on the datasource. This can include tasks
// such as validating the data, applying business rules, modifying field values, or any other behavior that should be 
//triggered before the write operation takes place.

[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::Writing)] 
public static void PurchParmTable_OnWriting(FormDataSource sender, FormDataSourceEventArgs e)  
{  
    FormDataSource CaseDetailBase_ds = sender.formRun().datasource(formDataSourceStr(purcheditlines,purchParmTable));
    CaseDetailBase_ds.refresh();
    CaseDetailBase_ds.research(true);
}    

25.OnWritten.
//The primary purpose of the OnWritten event handler at the datasource level is to handle specific actions
 //or modifications that need to occur after a write operation has been performed on the datasource.
 //This can include tasks such as refreshing dependent controls, updating related data, triggering notifications,
 //or any other behavior that should be triggered after the write operation has completed.
 
[FormDataSourceEventHandler(formDataSourceStr(PurchEditLines, PurchParmTable), FormDataSourceEventType::Written)]
public static void PurchParmTable_OnWritten(FormDataSource sender, FormDataSourceEventArgs e) 
{  
	formrun  formrun = sender.formRun();
	PurchParmTable  purchparmtable = sender.cursor(); 
	//Write your code
}
--------------------------------------------------------------------------------------------------------------------------------------
26.OnModified
[FormDataFieldEventHandler(formDataFieldStr(PurchEditLines, PurchParmTable, BankLCImportLine), FormDataFieldEventType::Modified)]
public static void BankLCImportLine_OnModified(FormDataObject sender, FormDataFieldEventArgs e)
{    
	FormDataSource          purchparmtable_ds   = sender.datasource(); // datasource. 
	PurchParmTable  purchparmtable = purchparmtable_ds.cursor(); // active record.  
	if (purchparmtable.PurchId)  
	{   
		purchparmtable.PurchName = "";   
	}
}

Keep learning!!