Monday, June 24, 2024

D365 F&O Build Pipeline failed after upgrade to 10.0.40

 

After upgrading the D365 F&O CHE Build Server from 10.0.38 to 10.0.40, our Build Pipeline failed. 

Error Message: 

error MSB1009: Project file does not exist.

C:\DynamicsSDK\Metadata\Microsoft.Dynamics.AX.Application.Build.targets(40,5): error : Error: dynamics://Class/XYZTable_VendInvoiceInfoTable_Extension/Method/getCheckDate(11,5) - A reference to 'Dynamics.AX.ProcessAutomation, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is required to compile this module. [C:\DynamicsSDK\Metadata\BuildMetadata.proj]


Here are the steps we did:

1. Apply 10.0.40 to a CHE Dev env (it was on 10.0.38)
2. Compile clean
3. Apply ISV update 
4. Compile clean
5. Apply 10.0.40 to the CHE Build Server,it  went clean
6. Check-in the ISV updates to the ADO branch
7. Kick off the Build Pipeline and it failed with the above error message

Fix: In the XYZ custom model, add a reference to the "Process Automation" standard model.

Sunday, March 26, 2023

Dual-write application configuration failed

 Hello D365 Community

Point to remember - We can set up Power Platform Integration only during the deployment of the cloud-hosted environment.  So, Power Platform Integration can't be set up after the developer environment is created.

For example, here is a screenshot of the Dev environment in LCS, for which I had set "Configure Power Platform" to No during the env creation. And because of this, the Dual-Write button is missing:



Ref: Enable Power Platform Integration - Finance & Operations | Dynamics 365 | Microsoft Learn

Now, let's move to main subject. I created a new Cloud hosted env with "Configure Power Platform" set to Yes in the deployment wizard.  This went well and as expected I see "Setup Dual-Write application".




Now that env is created, I performed following actions in the env:
  • Link it to DevOps and got latest code from Dev branch.
  • Restore DB backup from our UAT/Sandbox environment.
  • Compile the whole Application.
  • DB sync

Then in LCS, I clicked the button "Setup Dual-Write application" and after few minutes it failed with this message.

Status:  Dual-write application configuration failed. Please click on resume dual write application to try again.
Failure reason: Script [RefreshAXEntities-AOS-Dev-0] failed execution against VM [ABC-Dev03-1]. Last Result: 0xFFFFFFFF ()




I tried "Resume Dual-Write application" with no success.

Fix:  Enable Change Tracking on the SQL Server DB


ALTER DATABASE AxDB
SET CHANGE_TRACKING = ON  
(CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON)  


Now on clicking "Resume Dual-Write application", it succeeded.

Here is how I found the root cause: 
  • In the FO application, manually ran the "Refresh Entity List" (Location: Data Management workspace -> Framework parameters -> Entity List)
  • The batch job failed with the error "Change Tracking is not enabled on the database: SQL Server Change Tracking"

 Happy coding

Sunday, January 29, 2023

Search for an object in F&O VS Projects Folder

 Sometimes F&O Developers may want to find the Projects in which a particular element was referred.

For example, a MenuItem/Table/Data Entity element that was not checked into the Azure DevOps source control and the original developer is not reachable immediately. 

Then this tip may come in handy...



In this case, I am trying to find the Projects where the custom view "ST5CustDeliveryAddressView" is referred.


My Projects root folder:




Search for the custom view "ST5CustDeliveryAddressView", but no results.



No luck even after selecting the "File contents" in Windows


 

Here is the solution:

Extension for F&O Visual Studio Project file is "rnrproj":




In Windows Explorer, navigate to F&O VS Projects root folder -> Search -> Advanced Options -> Change Indexed Locations

1. Click Modify 

2. add F&O VS Project folder to the existing list of Indexed Locations.





You could open "Indexing Options" tool from the Windows search bar too.


3. Click the Advanced button -> Advanced options dialog -> tab File Types -> 

4. In the text box for "Add new extension to list", key-in rnrproj and click Add


5. Enable "Index Properties and File Contents 

6. Click OK -> Close "Indexing Options" tool

7. Wait for a min, so windows can index the Projects directory

8. Now search for the object/element and you will see the Projects that refer to the element.




Happy Coding.





Tuesday, January 10, 2023

Using Power Platform Data Integrator to integrate F&O Delivery Address Location with CE Account

In this article, we will see how to Integrate D365 F&O Customer Delivery Address Location with D365 CE  Account via Power Platform's Data Integrator. This is going to be a one-way integration from F&O to CE, but you can expand the solution to be bi-directional.

Here is the recording of this Demo:






Low-Code options within the MS ecosystem for the Integration layer:

  1. Dual-Write
  2. Power Platform Data Integrator (approach used in this Demo)
  3. Data Events through Virtual Entity and Power Automate

Limitation in Dual-Write based on my trial:


In F&O Location is Global data and in CE Account is Business Unit specific, this case seems to be not a fit for the Dual-Write framework.  

On trying to save the table map in Dual-Write, this is the error message I received:

Project validation failed. [DIPV1002] ST5CustDeliveryAddressEntity or AX is a cross-company entity that doesn't have primary company field set and accounts of CRM is a company-specific entity with primaryCompanyField set to msdyn_company.cdm_companycode. Please make sure the entities are both cross-company or company-specific entities.

Looks like it is a pre-requisite in Dula-Write, that either both the entities need to be Global or Legal entity specific.

So, I moved on to Power Platform's Data Integrator approach.


Power Platform's Data Integrator approach:

Environments Set-up:

  • I have setup Contoso demo data in F&O 
  • I have setup CE Dataverse to be blank data.
  • Create USMF BU in Dataverse manually (or go through Dual-Write setup which would create this for you).
  • Manually Created a new Customer "San-1" in F&O
        



  • Manually Created a new account "San-1" in CE
            




Security in Dataverse:

In Power Platform, make sure in each Business Unit the default Team has System Admin role. Integration layer uses the default Team of the BU in context.

At the minimum, do this for root BU and USMF BU




F&O Custom Data Entity:

In F&O, I created a custom Data Entity ST5CustDeliveryAddressEntity to expose Customer Delivery Addresses:

Data sources used:

  • LogisticsPostalAddress
  • LogisticsLocation
  • DirPartyLocation
  • CustTable

At the minimum, we would expose the following fields:

  • LocationId (from LogisticsLocation)
  • Description (from LogisticsLocation)
  • AccountNum (from CustTable)
Key: LocationId, ValidFrom

Range: On DirPartyLocation.IsRoleDelivery set to true

Verify the data exposed by OData API, it should render only Customer Delivery Address Locations:

https://<FO instance name>.axcloud.dynamics.com/data/ST5CustDeliveryAddresses

Now Add new Delivery Address with name "Chicago Del-3" on the Customer San-1


Verify that Delivery Address with name "Chicago Del-3" shows up in the OData API output:

https://<FO instance name>.axcloud.dynamics.com/data/ST5CustDeliveryAddresses?$filter=AccountNum%20eq%20%27San-1%27



Data Integrator Project in Power Platform Admin Portal:

In Power Platform Admin Portal -> Data Integration:

  • Create a new Connection Set, to establish a connection between F&O instance and CE instance
    • Here we would select the Organization to be USMF
  • Create a new Project (name could be FO to CE OOB)
    • Associate the Connection set created earlier
    • Select the template to be "Fin and Ops to Sales"
    • Select Organization to be USMF



  • Click the Project created -> Add Task -> 
    • Task Name: FO Delivery Address to CE Account
    • F&O Table: ST5CustDeliveryAddressEntity 
    • CE Table: Accounts
    • Org: USMF
  • Click the Task to add field mappings as per below screenshot

            


  •     Click Filter icon on Source, add following filter:  ((ACCOUNTNO eq "San-1"))
               This way integration fetches only Delivery Addresses related to San-1 customer, to keep the demo simple. You could also use "Advanced query and filtering" feature, which is more like Power Query.

  • Save the Task and Run the Project manually

            



  • Go to Execution History Tab, the run instance would be in status running. 
  • After couple of mins the status would change to Completed
        
        


   



Now go CE All Accounts page, we would see the new Account with name "Chicago Del-3"




Hope this blog comes in handy for the community. 

In the later series, I may try to come up with a similar demo using Data Events (via Virtual Entity) approach.

References:

Data Integrator: Integrate data into Microsoft Dataverse - Power Platform | Microsoft Learn


Thursday, August 25, 2016

Catch exact error message from AX AIF service


when interacting with AX AIF services to catch the exact Error message this is what you would do in your C# Catch block


catch(FaultException<AifFault> ex)
            {
                InfologMessage[] infoLogList = ex.Detail.InfologMessageList;
                foreach (var info in infoLogList)
                {
                    Console.WriteLine(info.Message);
                }

                proxy.Abort();
                Console.ReadLine();
            }

Wednesday, July 27, 2016

Using Customer AIF Service in AX 2012

In this post i would be demonstrating how to use out of box Customer Service (CustCustomerService) for the following operations:

  1. Read a Customer
  2. Create a Customer
  3. Update a Customer
  4. Delete a Customer
  5. Find Customers
  6. FindKeys for Customers
CustService is the alias name i used in my code to refer Customer Service;

using CustService = ConsumeAXServices.CustomerServiceRef;

Here is the C# method to Read a Customer:

public  CustService.AxdCustomer  CustomerRead(ref CustService.EntityKey[] entityKeyList, string accNum =  "US-003" , Boolean halt = true)
        {
            CustService.CustomerServiceClient proxy = null;
            CustService.AxdCustomer custDoc = null;
            CustService.CallContext callContext = null;

            try
            {
                custDoc = new CustService.AxdCustomer();                

                CustService.EntityKey[] custKeyList = new CustService.EntityKey[1];
                CustService.EntityKey custKey = new CustService.EntityKey();
                custKey.KeyData = new CustService.KeyField[1];

                CustService.KeyField custAccountNum = new CustService.KeyField();
                custAccountNum.Field = "AccountNum";
                custAccountNum.Value = accNum;

                custKey.KeyData[0] = custAccountNum;
                custKeyList[0] = custKey;

                callContext = new CustService.CallContext() { Company = "usmf" };
                proxy = new CustService.CustomerServiceClient();             

                custDoc = proxy.read(callContext, custKeyList);
                Console.WriteLine("Cust Num - " + custDoc.CustTable[0].AccountNum);
                Console.WriteLine("Cust CreditStatus - " + custDoc.CustTable[0].CreditRating);
                
                if (halt)
                    Console.ReadLine();

                entityKeyList = custKeyList;
                
            }

            catch (Exception ex)
            {
                Console.WriteLine("Error - " + ex.Message);
                proxy.Abort();
                Console.ReadLine();

            }
            finally
            {
                if (proxy != null)
                {
                    proxy.Close();
                    proxy = null;
                }
            }

            return custDoc;
        }


Here is the C# method to Create a Customer:


public void CreateCustomer(string accNum = "SanCus003", string custName = "Santosh Cust 003")
        {
            CustService.CustomerServiceClient proxy = null;
            CustService.AxdCustomer custDoc;
            CustService.CallContext callContext = null;
            CustService.AxdEntity_CustTable custTable = null;
            CustService.AxdEntity_DirParty_DirOrganization dirPartyOrg = null;
            CustService.AxdEntity_OrganizationName orgName = null;           


            try 
            {
                custDoc = new CustService.AxdCustomer();

                custTable = new CustService.AxdEntity_CustTable() { AccountNum = accNum, CustGroup = "20" };
                
                dirPartyOrg = new CustService.AxdEntity_DirParty_DirOrganization() { NumberOfEmployees = 11, NumberOfEmployeesSpecified = true };
                orgName = new CustService.AxdEntity_OrganizationName { Name = custName };
                dirPartyOrg.OrganizationName = new CustService.AxdEntity_OrganizationName[] { orgName };


                custDoc.CustTable = new CustService.AxdEntity_CustTable[] { custTable};
                custTable.DirParty = new CustService.AxdEntity_DirParty_DirPartyTable[] { dirPartyOrg };

                callContext = new CustService.CallContext() { Company = "usmf" };
                proxy = new CustService.CustomerServiceClient();
                var entityKeyList =  proxy.create(callContext, custDoc);

                var keyField = entityKeyList.FirstOrDefault().KeyData.FirstOrDefault();
                Console.WriteLine("Key Field - " + keyField.Field + "Key Value - " + keyField.Value);
                Console.ReadLine();

            }

// removed Catch and finally block to save the space



Here is the C# method to Update a Customer:

public void UpdateCustomer(string accNum = "SanCus002", string CustName = "Santosh Cust 002 - Update3")
        {
            CustService.CustomerServiceClient proxy = null;
            CustService.AxdCustomer custDoc = null;
            CustService.CallContext callContext = null;
            CustService.AxdEntity_CustTable custTable = null;
            CustService.AxdEntity_DirParty_DirOrganization dirPartyOrg = null;
            CustService.AxdEntity_OrganizationName orgName = null;

            CustService.EntityKey[] entityKeyList = null; 


            try
            {
               custDoc = this.CustomerRead(ref entityKeyList, accNum, false);

               custTable = custDoc.CustTable[0];
               custTable.CreditMax = 10000;
               custTable.action = CustService.AxdEnum_AxdEntityAction.update;
               custTable.actionSpecified = true;               
                
               dirPartyOrg = custDoc.CustTable[0].DirParty[0] as CustService.AxdEntity_DirParty_DirOrganization;                               
               dirPartyOrg.NumberOfEmployees = 12;
               dirPartyOrg.NumberOfEmployeesSpecified = true;
               dirPartyOrg.action = CustService.AxdEnum_AxdEntityAction.update;
               dirPartyOrg.actionSpecified = true;                         
               
               orgName =  dirPartyOrg.OrganizationName[0];
               orgName.updateMode = CustService.AxdEnum_ValidTimeStateUpdate.Correction;
               orgName.updateModeSpecified = true;
               orgName.Name = CustName;
               orgName.action = CustService.AxdEnum_AxdEntityAction.update;
               orgName.actionSpecified = true;
                

               callContext = new CustService.CallContext() { Company = "usmf" };
               proxy = new CustService.CustomerServiceClient();
               proxy.update(callContext, entityKeyList, custDoc);
                
               Console.WriteLine("Customer Update Success");
               Console.ReadLine();

            }

// removed Catch and finally block to save the space



Here is the C# method to Delete a Customer (customer shouldn't have any transactions assoicated):

public void DeleteCustomer(string accNum = "US-003")
        {
            CustService.CustomerServiceClient proxy = null;
            CustService.CallContext callContext = null;
            
            try
            {
                CustService.EntityKey[] custKeyList = new CustService.EntityKey[1];
                CustService.EntityKey custKey = new CustService.EntityKey();
                custKey.KeyData = new CustService.KeyField[1];

                CustService.KeyField custAccountNum = new CustService.KeyField();
                custAccountNum.Field = "AccountNum";
                custAccountNum.Value = accNum;

                custKey.KeyData[0] = custAccountNum;
                custKeyList[0] = custKey;

                callContext = new CustService.CallContext() { Company = "usmf" };
                proxy = new CustService.CustomerServiceClient();
                proxy.delete(callContext, custKeyList);

                Console.WriteLine("Customer Delete Success - " + accNum);
                Console.ReadLine();
            }
// removed Catch and finally block to save the space

     


Here is the C# method to Find Customers based on a Criteria

        public void FindCustomers(string custGrp = "10")
        {
            CustService.CustomerServiceClient proxy = null;
            CustService.CallContext callContext = null;
            CustService.QueryCriteria qc = null;
            CustService.AxdCustomer custDoc = null;
            CustService.AxdEntity_CustTable[] custTableList = null;

            try 
            {
                CustService.CriteriaElement criteria = new CustService.CriteriaElement() 
                                                    { DataSourceName = "CustTable", FieldName = "CustGroup", Value1 = custGrp };

                CustService.CriteriaElement[] criteriaList = new CustService.CriteriaElement[] { criteria };
                qc = new CustService.QueryCriteria();
                qc.CriteriaElement = criteriaList;

                callContext = new CustService.CallContext() { Company = "usmf" };
                proxy = new CustService.CustomerServiceClient();
                custDoc = proxy.find(callContext, qc);

                custTableList = custDoc.CustTable;
                foreach (var custTable in custTableList)
                {
                    Console.WriteLine("Customer - {0} , Group - {1}" , custTable.Name, custTable.CustGroup);                    
                }

                Console.ReadLine();                
            }
// removed Catch and finally block to save the space





Here is the C# method to FindKeys of Customers based on a Criteria:

        public void FindKeysCustomer(string custGrp = "10")
        {
            CustService.CustomerServiceClient proxy = null;
            CustService.CallContext callContext = null;
            CustService.QueryCriteria qc = null;
            CustService.EntityKey[] custKeyList = null;            

            try
            {
                CustService.CriteriaElement criteria = new CustService.CriteriaElement() { DataSourceName = "CustTable", FieldName = "CustGroup", Value1 = custGrp };

                CustService.CriteriaElement[] criteriaList = new CustService.CriteriaElement[] { criteria };
                qc = new CustService.QueryCriteria();
                qc.CriteriaElement = criteriaList;

                callContext = new CustService.CallContext() { Company = "usmf" };
                proxy = new CustService.CustomerServiceClient();
                custKeyList  = proxy.findKeys(callContext, qc);

                foreach (var custKey in custKeyList)
                {
                    CustService.KeyField keyField = custKey.KeyData.FirstOrDefault();
                    Console.WriteLine("Key Field - {0} , Key Value - {1}", keyField.Field, keyField.Value);                    
                }

                Console.ReadLine();

            }
// removed Catch and finally block to save the space








Saturday, July 9, 2016

AX 7 Tips

Tip1: How to run a Runnable class directly from Web browser:
syntax: https://<aosUrl>/?mi=SysClassRunner&cls=<RunnableClassName>

Ex:
https://usnconeboxax1aos.cloud.onebox.dynamics.com/?mi=SysClassRunner&cls=WipRunClass1

an FYI...Runnable is just a regular class with Main method (main method is the starting point)



Tip2: AX dlls needed in a C# Project:



Tip3: Not able to break into X++ code or C# code, then here is your solution:
You may need to manually load the debug Info files(.dll, .pdb, .netModule)  through the VS -> Debug -> Modules window -> Search for C# namespace or X++ class name) -> Load Symbols




Tip4:   you can manually attach the VS debugger to worker process : VS -> Debug -> Process -> W3Wp.exe . In the case of AX 2012 you would attach the VS debugger to "AX32Serv.exe" process to debug anything that always runs in CLR, Example: Services.
(Also you don't need to set an aot element as "Start Up Object" in VS every time, you just need to know how run your object directly from the web browser)





Wednesday, June 29, 2016

AX 2012 known issues

Error1:
Team Foundation services are not available from server

you may get this error  for multiple reasons and one of the reason could be that your TFS may have multiple Project Collections and your AX Team Project is not in the Default Project collection.

Solution:  make sure to include TFS Project Collection name in the TFS URL : 
http://tfs:8080/tfs/<ProjectCollectionName>




Where is Tile Button control in AX 7

Tile Button control is part of  "Application Foundation" Model. This means unless your project has a reference to this model you wouldn't be able to add this Tile button control on any form.

If you need to refer more models in your project then go through Update Model Parameters (Dynamics AX Menu)  wizard and select the models you need.

Thursday, April 14, 2016

Item Service in AX 2012- This document does not support the AxEcoResProductIdentifier class or AxEcoResProductCategory class

In AX 2012's Item service as part of creating an Item/Product through AIF Service if you try to populate entities EcoResProductIdentifier, EcoResProductCategory you may receive these errors:

This document does not support the AxEcoResProductIdentifier class
or
This document does not support the AxEcoResProductCategory class

You may very well receive these errors after going through the AIF wizard and regenerating the Item Service (InventTable and EcoResProduct), the reason being wizard doesn't generate some code related to all the Tables involved in the AxdQuery.

To fix this, I had to add following code in class -> AxdEcoResProduct-> prepareForSave()

       case classNum(AXEcoResProductIdentifier):
            axecoResProductIdentifier   = _axdStack.top() as AxEcoResProductIdentifier;
            axEcoResProduct             = axecoResProductIdentifier.parentAxBC() as AxEcoResProduct;
            axecoResProductIdentifier.parmProduct(axEcoResProduct.parmRecId());
            return true;       
        
        case classNum(AxEcoResProductCategory):
            axEcoResProductCategory     = _axdStack.top() as AxEcoResProductCategory;
            axEcoResProduct             = axEcoResProductCategory.parentAxBC() as AxEcoResProduct;
            axEcoResProductCategory.parmProduct(axEcoResProduct.parmRecId());
            return true;       


Ofcourse I had declare the corresponding variables.
Happy Daxing.....




Tuesday, October 7, 2014

AIF Queue Manager is not retaining Erred Request messages


In AIF Asynchronous  integration port (Eg: File Adapter) Queue Manager doesn't  retain Erred incoming request messages if the field "respond after error in aysnchronous request" on the Integration port is checked.


The reason being is - for AIF to respond it needs to do outbound processing, once this field is unchecked on the Integration port AIF doesn't do outbound processing  for erred request messages and therefore message will remain in the Queue Manager.




Tuesday, October 15, 2013

Customers with no Transactions in X++

The reason i am posting this is because i think this is an X++ query limitation in AX 2009....Let's start with my requirement:
In my AX environment, CustTable is associated with a Virtual company and the virtual company is associated with all the real companies. CustTrans is per company and my requirement is to find the Customers with no transaction at all. The X++ query that i came up with is (simple):

while select crosscompany _CustTable
   notexists join _CustTrans
   where _CustTable.AccountNum == _CustTrans.AccountNum
       {
        i++;
        // some process
       }

the way AX interprets this query is :
(Customers with no transactions in company A)  Union  (Customers with no transactions in company B) Union ........
this doesn't solve my requirement because a customer who doesn't have transactions in company A may have transactions in other companies.

There is no simple and efficient way to put this in X++  query so i used direct SQL query in X++ which came out to be very efficient in my case.Here is the SQL query i used:

SELECT A.ACCOUNTNUM,A.NAME
   FROM CUSTTABLE A
   WHERE NOT EXISTS
    (SELECT 'x' FROM CUSTTRANS B WHERE (A.ACCOUNTNUM=B.ACCOUNTNUM))


Here is the approach i used in X++:

void ExportCustomersWithNoTrans()
{
   System.IO.StreamWriter sw;
   InteropPermission permIO = new InteropPermission(InteropKind::ClrInterop);
   Connection dbConnection;
   Statement  sqlStatement;
   str        sqlStr;
   ResultSet  resultSet;
   SqlStatementExecutePermission permSQL;
   int counter;
   CustTable custTable;
   CustAccount currCustAccountNum;
    ;

    info(strFmt('Start Time for CustomersWithNoTrans - %1', DateTimeUtil::getSystemDateTime() ) );

    dbConnection = new Connection();
    sqlStr = 'SELECT A.AccountNum FROM CUSTTABLE A ';
    sqlStr += 'Where NOT EXISTS ';
    sqlStr += '(SELECT \'x\' FROM CUSTTRANS B WHERE  (A.ACCOUNTNUM=B.ACCOUNTNUM)) ';

    permSQL = new SqlStatementExecutePermission(sqlStr);
    permSQL.assert();
    sqlStatement = dbConnection.createStatement();
    resultSet = sqlStatement.executeQuery(sqlStr);
    CodeAccessPermission::revertAssert();

    permIO.assert();
    sw = new System.IO.StreamWriter(@"C:\Data\realTemp\CustomersWithNoTrans.txt");

    while(resultSet.next() )
    {
      currCustAccountNum = resultSet.getString(1);
      custTable = null;
      select custTable where custTable.AccountNum == currCustAccountNum;

      sw.WriteLine( strFmt('%1 \, %2', custTable.AccountNum , custTable.Name) );
    }

    sw.Flush();
    sw.Close();
    sw.Dispose();

    CodeAccessPermission::revertAssert();
    info(strFmt('End Time for CustomersWithNoTrans - %1', DateTimeUtil::getSystemDateTime() ) );

}

So, looks like the combination of direct SQL and X++ query is the way to go if  we come across X++ limitations or performance issues.

Hope you find this post to be useful. Happy DAXing......

Tuesday, September 25, 2012

CreateSalesLine form in Dynamics AX(just like SalesCreateOrder form)

This post is about creating a New CreateSalesLine form which is just going to work like SalesCreateOrder form except that this out of box form creates SO and the new one create SalesLine.

Let's start with the reasons why we may need this form - In out of box SalesTable form
1.  whenever user clicks on the new button from toolbar all the user filters and sort expressions are lost and the active line item that user wants to refer will loose the focus, this is all because the datasource query re-executes. I tried to persist the user filters using my earlier post technique, but it didn't work for me in the case of new button.
2. since it is about referring an existing lineItem we can always use out of box "Copy from lines" function, but the issue with this one is - it doesn't allow user to change the ItemId of the line ( my user wants to use the values from existing lineItem for a new lineItem with different ItemId). i tried to solve this one by making ItemId field editable in the Salestable form, but it was causing chain of other errors - let me know if you have any luck with this.
So i came up with this approach where i provide a new button named "New LineItem" in the salesLine section of SalesTable form(Fig 1), on click i will pop-up the new form that i call CreateSalesLine (Fig 2)

Fig 1                                                                                                        Fig 2:
SalesLine section in SalesTable form (Parent)


New form - CreateSalesLine (Child)
this form will create a new line item record and pre-populates it with values from the active lineItem record in Parent form(SalesTable). User can change the item number if needed and once OK button is clicked the newly created lineItem record is commited to DB , child form closes and the SalesLine grid in the parent form refreshes with the focus on newly Created LineItem.

Here are the things i did to achieve this :
1. To simplify i have created this simple data flow diagram

2. within the new button's click event , i added :

   CreateSalesLineClass obj;
    ;   
    obj = new CreateSalesLineClass();
    obj.init(salesTable, salesTableForm, inventDim, salesLine);  // passing current records info
    obj.create();  // we will discuss this method in subsequent steps
     SalesTable_ds.research(true);
     SalesLine_ds.research(true);
     SalesLine_ds.findRecord(obj.newSalesLineRec());   // to focus the newly created record
     super();

3. CreateSalesLineClass:  
 a)  variables declared at class level scope:
SalesTable salesTable;
  SalesTableForm salesTableForm;
  InventDim  inventDim;
  SalesLine copyFromSalesLine;
  Int64 newLineRecId;
  SalesLine newSalesLineRec;

b) init method:
void init(SalesTable _currRec, SalesTableForm _salesTableForm, InventDim _inventDim, SalesLine _copyFromSalesLine)
{
  ;
  salesTable = _currRec;
  salesTableForm = _salesTableForm;
  inventDim = _inventDim;
  copyFromSalesLine = _copyFromSalesLine;
}

c) Create method:
boolean create()
{
   Args args = new Args();
   FormRun CreateSalesLine;
   SalesLine salesLine;
   boolean result = false;
   SalesTable localSalesTable;
   ;

   args.name(formStr(CreateSalesLine) );
   args.caller(this);
   CreateSalesLine = classFactory.formRunClass(args);
   CreateSalesLine.init();
   CreateSalesLine.run();

   if(!CreateSalesLine.closed())
        CreateSalesLine.wait();

   if(CreateSalesLine.closedOk())
         result = true;
   else
          result = false;

   return result;  // indicating if the new record is created or not
}


4. CreateSalesLine form:

a) Add SalesLine datasource , add simple design tab control with some salesLine fields (Fig 2) and add Command buttons - OK and Cancel

b) CreateSalesLine form class declaration:

   CreateSalesLineClass  CreateSalesLineClass;
   SalesTable salesTable;
   SalesTableForm salesTableForm;
   InventDim copyFromInventDim , inventDim;
   SalesLine copyFromSalesLine, newlyCreatedSalesLine ;


c) init method:
public void init()
{
    super();
     if (!element.args().caller())  // throw error if called directly
        throw error("@SYS22539");

    CreateSalesLineClass = element.args().caller();
    salesTable = CreateSalesLineClass.getSalesTable();
    salesTableForm = CreateSalesLineClass.getSalesTableForm();
    copyFromInventDim = CreateSalesLineClass.getInventDim();
    copyFromSalesLine = CreateSalesLineClass.getCopyFromSalesLine();
}


d) run method:
public void run()
{
    super();
    SalesLine_ds.create(); 
    CreateSalesLineClass.newSalesLineRec(SalesLine);   // pass the newly created salesline ref to caller
}

e) SalesLine_DS -> Create method:
public void create(boolean _append = false)
{
    super(_append);
    newlyCreatedSalesLine.data(SalesLine); // back up the newly created record
    newlyCreatedInventDim.data(inventDim); // back up the newly created record
   // we can use both the back-up buffers later if User changes the ItemId

    // set the basic Line info
    SalesLine.SalesId = salesTable.SalesId;
    SalesLine.ItemId = copyFromSalesLine.ItemId;

    inventDim.InventColorId = copyFromInventdim.InventColorId;
    inventDim.InventLocationId = copyFromInventDim.InventLocationId;
    inventDim = InventDim::findOrCreate(inventDim);    // create new inventDim rec

    SalesLine.InventDimId = inventDim.inventDimId;

    SalesLine.CustAccount = salestable.CustAccount;
    SalesLine.CurrencyCode = salesTable.CurrencyCode;
    SalesLine.CustGroup  = salesTable.CustGroup;
    SalesLine.SalesType  = salesTable.SalesType;

    // copy data from active salesLine record in parent form, i just copied the data that my client needs
    SalesLine.ReceiptDateRequested  = copyFromSalesLine.ReceiptDateRequested;
    SalesLine.ShippingDateRequested = copyFromSalesLine.ShippingDateRequested;

    salesLine.SalesQty           =  copyFromSalesLine.SalesQty;
    salesLine.SalesUnit          = copyFromSalesLine.SalesUnit;
    salesLine.QtyOrdered         = copyFromSalesLine.QtyOrdered;

    salesLine.SalesPrice         = copyFromSalesLine.SalesPrice;
    salesLine.PriceUnit          = copyFromSalesLine.PriceUnit;
    salesLine.LinePercent        = copyFromSalesLine.LinePercent;
    salesLine.LineDisc           = copyFromSalesLine.LineDisc;
    salesLine.MultiLnDisc        = copyFromSalesLine.MultiLnDisc;
    salesLine.MultiLnPercent     = copyFromSalesLine.MultiLnPercent;
    salesLine.SalesMarkup        = copyFromSalesLine.SalesMarkup;
    salesLine.LineAmount         = copyFromSalesLine.LineAmount;
    // you can copy more data as per your need , refer class SalesLineType.initFromSalesLine() - used by out of box copy line functionality
}


f) Override form's salesLine datasource's Write method:
public void write()
{
   ; 
     if (!element.closedOk())
          return;
     SalesLine.createLine(NoYes::Yes, NoYes::No, NoYes::No, NoYes::No);
     super();
}

so far whatever i coded is good as long as User doesn't want to change the ItemId in the new form, my user like the ability to change the ItemId so i coded the following...


g) override the modified method for ItemId field of SalesLine datasource:

public void modified()
{
    ItemId itemId;
    ;
    super();

    //info(strFmt('Old ItemId - %1',  copyFromSalesLine.ItemId ));
    //info(strFmt('New ItemId - %1',  SalesLine.ItemId ));

    itemId = SalesLine.ItemId;
    SalesLine.data(newlyCreatedSalesLine);  // restore from the back-up buffer
    inventDim.data(newlyCreatedInventDim);
    SalesLine.ItemId = itemId;
    SalesLine_ds.refresh();  // refresh form data from the cache

    info(strFmt('Old ItemId - %1',  copyFromSalesLine.ItemId ));

    inventDim = InventDim::findOrCreate(inventDim);//inventDim.data(InventDim::find(salesLine.InventDimId));
    salesLine.modifyItemDim(inventDim, fieldnum(InventDim,InventLocationId),  InventTable::find(salesLine.ItemId).DimGroupId);
    InventMovement::bufferSetTransQtyUnit(salesLine);

    salesLine.setDeliveryDateControlType(InventDim::findOrCreate(inventDim).InventDimId);
    SalesLine::modifySalesQty(salesLine,inventDim);
    SalesLine_ds.refresh();

    //info( strFmt('AfterChange Invent Color and Location: %1 and %2', inventDim.InventColorId, inventDim.InventLocationId) );

    // copying back
    SalesLine.SalesId = salesTable.SalesId;
    SalesLine.CustAccount = salestable.CustAccount;
    SalesLine.CurrencyCode = salesTable.CurrencyCode;
    SalesLine.CustGroup  = salesTable.CustGroup;
    SalesLine.SalesType  = salesTable.SalesType;

    SalesLine.ReceiptDateRequested  = copyFromSalesLine.ReceiptDateRequested;
    SalesLine.ShippingDateRequested = copyFromSalesLine.ShippingDateRequested;

    salesLine.SalesQty           =  copyFromSalesLine.SalesQty;
    salesLine.SalesUnit          = copyFromSalesLine.SalesUnit;
    salesLine.QtyOrdered         = copyFromSalesLine.QtyOrdered;

    SalesLine_ds.refresh();
}

Comments, questions are always welcome, happy DAXing :)